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

voku / Arrayy / 36041130917

24 Sep 2026 06:25PM UTC coverage: 92.662% (+0.3%) from 92.38%
36041130917

Pull #183

github

web-flow
Merge 581e74a95 into 17d12ec25
Pull Request #183: Add PHPStan baseline and Infection CI wiring

21 of 21 new or added lines in 4 files covered. (100.0%)

3 existing lines in 2 files now uncovered.

2778 of 2998 relevant lines covered (92.66%)

143.58 hits per line

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

92.44
/src/Arrayy.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Arrayy;
6

7
use Arrayy\TypeCheck\TypeCheckArray;
8
use Arrayy\TypeCheck\TypeCheckInterface;
9
use Arrayy\TypeCheck\TypeCheckPhpDoc;
10

11
/**
12
 * Arrayy - Methods to manage arrays.
13
 *
14
 * ## πŸ‡·πŸ‡Ί Русским Π³Ρ€Π°ΠΆΠ΄Π°Π½Π°ΠΌ
15
 * Π’ Π£ΠΊΡ€Π°ΠΈΠ½Π΅ сСйчас ΠΈΠ΄Π΅Ρ‚ Π²ΠΎΠΉΠ½Π°. Π‘ΠΈΠ»Π°ΠΌΠΈ Π Π€ наносятся ΡƒΠ΄Π°Ρ€Ρ‹ ΠΏΠΎ граТданской инфраструктурС Π² [Π₯Π°Ρ€ΡŒΠΊΠΎΠ²Π΅][1], [КиСвС][2], [Π§Π΅Ρ€Π½ΠΈΠ³ΠΎΠ²Π΅][3], [Π‘ΡƒΠΌΠ°Ρ…][4], [Π˜Ρ€ΠΏΠ΅Π½Π΅][5] ΠΈ дСсятках Π΄Ρ€ΡƒΠ³ΠΈΡ… Π³ΠΎΡ€ΠΎΠ΄ΠΎΠ². Π“ΠΈΠ±Π½ΡƒΡ‚ люди - ΠΈ граТданскоС насСлСниС, ΠΈ Π²ΠΎΠ΅Π½Π½Ρ‹Π΅, Π² Ρ‚ΠΎΠΌ числС российскиС ΠΏΡ€ΠΈΠ·Ρ‹Π²Π½ΠΈΠΊΠΈ, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Ρ… бросили Π²ΠΎΠ΅Π²Π°Ρ‚ΡŒ. Π§Ρ‚ΠΎΠ±Ρ‹ Π»ΠΈΡˆΠΈΡ‚ΡŒ собствСнный Π½Π°Ρ€ΠΎΠ΄ доступа ΠΊ ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ, ΠΏΡ€Π°Π²ΠΈΡ‚Π΅Π»ΡŒΡΡ‚Π²ΠΎ Π Π€ Π·Π°ΠΏΡ€Π΅Ρ‚ΠΈΠ»ΠΎ Π½Π°Π·Ρ‹Π²Π°Ρ‚ΡŒ Π²ΠΎΠΉΠ½Ρƒ Π²ΠΎΠΉΠ½ΠΎΠΉ, Π·Π°ΠΊΡ€Ρ‹Π»ΠΎ нСзависимыС БМИ ΠΈ ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Π΅Ρ‚ сСйчас ряд диктаторских Π·Π°ΠΊΠΎΠ½ΠΎΠ². Π­Ρ‚ΠΈ Π·Π°ΠΊΠΎΠ½Ρ‹ ΠΏΡ€ΠΈΠ·Π²Π°Π½Ρ‹ Π·Π°Ρ‚ΠΊΠ½ΡƒΡ‚ΡŒ Ρ€ΠΎΡ‚ всСм, ΠΊΡ‚ΠΎ ΠΏΡ€ΠΎΡ‚ΠΈΠ² Π²ΠΎΠΉΠ½Ρ‹. Π—Π° ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹ΠΉ ΠΏΡ€ΠΈΠ·Ρ‹Π² ΠΊ ΠΌΠΈΡ€Ρƒ сСйчас ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ нСсколько Π»Π΅Ρ‚ Ρ‚ΡŽΡ€ΡŒΠΌΡ‹.
16
 * НС ΠΌΠΎΠ»Ρ‡ΠΈΡ‚Π΅! ΠœΠΎΠ»Ρ‡Π°Π½ΠΈΠ΅ - Π·Π½Π°ΠΊ вашСго согласия с ΠΏΠΎΠ»ΠΈΡ‚ΠΈΠΊΠΎΠΉ российского ΠΏΡ€Π°Π²ΠΈΡ‚Π΅Π»ΡŒΡΡ‚Π²Π°.
17
 *  **Π’Ρ‹ ΠΌΠΎΠΆΠ΅Ρ‚Π΅ ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ Π²Ρ‹Π±ΠΎΡ€ НЕ ΠœΠžΠ›Π§ΠΠ’Π¬.**
18
 * ---
19
 * ## πŸ‡ΊπŸ‡Έ To people of Russia
20
 * There is a war in Ukraine right now. The forces of the Russian Federation are attacking civilian infrastructure in [Kharkiv][1], [Kyiv][2], [Chernihiv][3], [Sumy][4], [Irpin][5] and dozens of other cities. People are dying – both civilians and military servicemen, including Russian conscripts who were thrown into the fighting. In order to deprive its own people of access to information, the government of the Russian Federation has forbidden calling a war a war, shut down independent media and is passing a number of dictatorial laws. These laws are meant to silence all those who are against war. You can be jailed for multiple years for simply calling for peace.
21
 * Do not be silent! Silence is a sign that you accept the Russian government's policy.
22
 * **You can choose NOT TO BE SILENT.**
23
 * ---
24
 * - [1] https://cloudfront-us-east-2.images.arcpublishing.com/reuters/P7K2MSZDGFMIJPDD7CI2GIROJI.jpg "Kharkiv under attack"
25
 * - [2] https://gdb.voanews.com/01bd0000-0aff-0242-fad0-08d9fc92c5b3_cx0_cy5_cw0_w1023_r1_s.jpg "Kyiv under attack"
26
 * - [3] https://ichef.bbci.co.uk/news/976/cpsprodpb/163DD/production/_123510119_hi074310744.jpg "Chernihiv under attack"
27
 * - [4] https://www.youtube.com/watch?v=8K-bkqKKf2A "Sumy under attack"
28
 * - [5] https://cloudfront-us-east-2.images.arcpublishing.com/reuters/K4MTMLEHTRKGFK3GSKAT4GR3NE.jpg "Irpin under attack"
29
 *
30
 * For the full copyright and license information, please view the LICENSE
31
 * file that was distributed with this source code.
32
 *
33
 * @template TKey of array-key
34
 * @template T
35
 * @template TData of array<TKey, T>
36
 * @extends \ArrayObject<TKey,T>
37
 * @implements \IteratorAggregate<TKey,T>
38
 * @implements \ArrayAccess<TKey,T>
39
 */
40
class Arrayy extends \ArrayObject implements \IteratorAggregate, \ArrayAccess, \Serializable, \JsonSerializable, \Countable
41
{
42
    const ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES = '!!!!Arrayy_Helper_Types_For_All_Properties!!!!';
43

44
    const ARRAYY_HELPER_WALK = '!!!!Arrayy_Helper_Walk!!!!';
45

46
    /**
47
     * @var array
48
     *
49
     * @phpstan-var array<array-key|TKey,T>
50
     */
51
    protected $array = [];
52

53
    /**
54
     * @var \Arrayy\ArrayyRewindableGenerator|null
55
     *
56
     * @phpstan-var \Arrayy\ArrayyRewindableGenerator<TKey,T>|null
57
     */
58
    protected $generator;
59

60
    /**
61
     * @var string
62
     *
63
     * @phpstan-var class-string<\Arrayy\ArrayyIterator<TKey,T>>
64
     */
65
    protected $iteratorClass = ArrayyIterator::class;
66

67
    /**
68
     * @var non-empty-string
69
     */
70
    protected $pathSeparator = '.';
71

72
    /**
73
     * @var bool
74
     */
75
    protected $checkPropertyTypes = false;
76

77
    /**
78
     * @var bool
79
     */
80
    protected $checkForMissingPropertiesInConstructor = false;
81

82
    /**
83
     * @var bool
84
     */
85
    protected $checkPropertiesMismatchInConstructor = false;
86

87
    /**
88
     * @var bool
89
     */
90
    protected $checkPropertiesMismatch = true;
91

92
    /**
93
     * @var array<array-key,TypeCheckInterface>|TypeCheckArray<array-key,TypeCheckInterface>
94
     */
95
    protected $properties = [];
96

97
    /**
98
     * @var array<string, true>
99
     */
100
    protected $optionalProperties = [];
101

102
    /**
103
     * Initializes
104
     *
105
     * @param mixed  $data                         <p>
106
     *                                             Should be an array or a generator, otherwise it will try
107
     *                                             to convert it into an array.
108
     *                                             </p>
109
     * @param string $iteratorClass                optional <p>
110
     *                                             You can overwrite the ArrayyIterator, but mostly you don't
111
     *                                             need this option.
112
     *                                             </p>
113
     * @param bool   $checkPropertiesInConstructor optional <p>
114
     *                                             You need to extend the "Arrayy"-class and you need to set
115
     *                                             the $checkPropertiesMismatchInConstructor class property
116
     *                                             to
117
     *                                             true, otherwise this option didn't not work anyway.
118
     *                                             </p>
119
     *
120
     * @phpstan-param TData|self<TKey,T,TData>|\Traversable<TKey,T>|callable|object|scalar|null $data
121
     * @phpstan-param class-string<\Arrayy\ArrayyIterator<TKey,T>> $iteratorClass
122
     */
123
    public function __construct(
124
        $data = [],
125
        string $iteratorClass = ArrayyIterator::class,
126
        bool $checkPropertiesInConstructor = true
127
    ) {
128
        $data = $this->fallbackForArray($data);
9,175✔
129

130
        // used only for serialize + unserialize, all other methods are overwritten
131
        /**
132
         * @psalm-suppress InvalidArgument - why?
133
         */
134
        parent::__construct([], 0, $iteratorClass);
9,161✔
135

136
        $this->setInitialValuesAndProperties($data, $checkPropertiesInConstructor);
9,161✔
137

138
        $this->setIteratorClass($iteratorClass);
8,972✔
139
    }
140

141
    /**
142
     * @return void
143
     */
144
    public function __clone()
145
    {
146
        if (!\is_array($this->properties)) {
371✔
147
            $this->properties = clone $this->properties;
7✔
148
        }
149

150
        if ($this->generator !== null) {
371✔
151
            $this->generator = clone $this->generator;
7✔
152
        }
153
    }
154

155
    /**
156
     * Call object as function.
157
     *
158
     * @param mixed $key
159
     *
160
     * @return mixed
161
     *
162
     * @phpstan-param TKey $key
163
     * @phpstan-return false|T|array<TKey,T>
164
     */
165
    public function __invoke($key = null)
166
    {
167
        if ($key !== null) {
7✔
168
            $this->generatorToArray();
7✔
169

170
            return $this->array[$key] ?? false;
7✔
171
        }
172

173
        /** @var array<TKey,T> $return */
174
        $return = $this->toArray();
×
175

176
        return $return;
×
177
    }
178

179
    /**
180
     * Whether or not an element exists by key.
181
     *
182
     * @param mixed $key
183
     *
184
     * @return bool
185
     *              <p>True is the key/index exists, otherwise false.</p>
186
     *
187
     * @phpstan-param TKey $key
188
     */
189
    public function __isset($key): bool
190
    {
191
        return $this->offsetExists($key);
7✔
192
    }
193

194
    /**
195
     * Assigns a value to the specified element.
196
     *
197
     * @param mixed $key
198
     * @param mixed $value
199
     *
200
     * @return void
201
     *
202
     * @phpstan-param TKey $key
203
     * @phpstan-param T $value
204
     */
205
    public function __set($key, $value)
206
    {
207
        $this->internalSet($key, $value);
14✔
208
    }
209

210
    /**
211
     * magic to string
212
     *
213
     * @return string
214
     */
215
    public function __toString(): string
216
    {
217
        return $this->toString();
105✔
218
    }
219

220
    /**
221
     * Unset element by key.
222
     *
223
     * @param mixed $key
224
     *
225
     * @phpstan-param TKey $key
226
     */
227
    public function __unset($key)
228
    {
229
        $this->internalRemove($key);
×
230
    }
231

232
    /**
233
     * Get a value by key.
234
     *
235
     * @param mixed $key
236
     *
237
     * @return mixed
238
     *               <p>Get a Value from the current array.</p>
239
     *
240
     * @template TAccessKey of key-of<TData>
241
     * @phpstan-param TAccessKey $key
242
     * @phpstan-return TData[TAccessKey]|null|self<array-key,T,array<array-key,T>>
243
     */
244
    public function &__get($key)
245
    {
246
        $return = $this->get($key, null, null, true);
1,174✔
247

248
        if (\is_array($return) === true) {
1,174✔
249
            $return = static::create(
×
250
                [],
×
251
                $this->iteratorClass,
×
252
                false
×
253
            )->createByReference($return);
×
254
        }
255

256
        return $return;
1,174✔
257
    }
258

259
    /**
260
     * Add new values (optional using dot-notation).
261
     *
262
     * @param mixed           $value
263
     * @param int|string|null $key
264
     *
265
     * @return static
266
     *                <p>(Immutable) Return this Arrayy object, with the appended values.</p>
267
     *
268
     * @phpstan-param T $value
269
     * @phpstan-param TKey $key
270
     * @phpstan-return static
271
     *
272
     * @psalm-mutation-free
273
     */
274
    public function add($value, $key = null)
275
    {
276
        if ($key !== null) {
98✔
277
            $get = $this->get($key);
35✔
278
            if ($get !== null) {
35✔
279
                $value = \array_merge_recursive(
7✔
280
                    !$get instanceof self ? [$get] : $get->getArray(),
7✔
281
                    !\is_array($value) ? [$value] : $value
7✔
282
                );
7✔
283
            }
284

285
            /* @phpstan-ignore argument.type */
286
            $this->internalSet($key, $value);
35✔
287

288
            return $this;
28✔
289
        }
290

291
        return $this->append($value);
63✔
292
    }
293

294
    /**
295
     * Append a (key) + value to the current array.
296
     *
297
     * EXAMPLE: <code>
298
     * a(['fΓ²Γ΄' => 'bΓ Ε™'])->append('foo'); // Arrayy['fΓ²Γ΄' => 'bΓ Ε™', 0 => 'foo']
299
     * </code>
300
     *
301
     * @param mixed $value
302
     * @param mixed $key
303
     *
304
     * @return $this
305
     *               <p>(Mutable) Return this Arrayy object, with the appended values.</p>
306
     *
307
     * @phpstan-param T $value
308
     * @phpstan-param TKey|null $key
309
     * @phpstan-return static
310
     */
311
    #[\ReturnTypeWillChange]
312
    public function append($value, $key = null): self
313
    {
314
        $this->generatorToArray();
147✔
315

316
        if ($this->properties !== []) {
147✔
317
            $this->checkType($key, $value);
49✔
318
        }
319

320
        if ($key !== null) {
140✔
321
            if (
322
                isset($this->array[$key])
14✔
323
                &&
324
                \is_array($this->array[$key])
14✔
325
            ) {
326
                $this->array[$key][] = $value; // @phpstan-ignore assign.propertyType
×
327
            } else {
328
                $this->array[$key] = $value;
14✔
329
            }
330
        } else {
331
            $this->array[] = $value;
126✔
332
        }
333

334
        return $this;
140✔
335
    }
336

337
    /**
338
     * Append a (key) + value to the current array.
339
     *
340
     * EXAMPLE: <code>
341
     * a(['fΓ²Γ΄' => 'bΓ Ε™'])->appendImmutable('foo')->getArray(); // ['fΓ²Γ΄' => 'bΓ Ε™', 0 => 'foo']
342
     * </code>
343
     *
344
     * @param mixed $value
345
     * @param mixed $key
346
     *
347
     * @return $this
348
     *               <p>(Immutable) Return this Arrayy object, with the appended values.</p>
349
     *
350
     * @phpstan-param T $value
351
     * @phpstan-param TKey $key
352
     * @phpstan-return static
353
     * @psalm-mutation-free
354
     */
355
    public function appendImmutable($value, $key = null): self
356
    {
357
        /**
358
         * @phpstan-return \Generator<TKey,T> $generator
359
         */
360
        $generator = function () use ($key, $value): \Generator {
7✔
361
            if ($this->properties !== []) {
7✔
362
                $this->checkType($key, $value);
×
363
            }
364

365
            foreach ($this->getGenerator() as $keyOld => $itemOld) {
7✔
366
                yield $keyOld => $itemOld;
7✔
367
            }
368

369
            if ($key !== null) {
7✔
370
                yield $key => $value;
×
371
            } else {
372
                yield $value;
7✔
373
            }
374
        };
7✔
375

376
        return static::create(
7✔
377
            $generator,
7✔
378
            $this->iteratorClass,
7✔
379
            false
7✔
380
        );
7✔
381
    }
382

383
    /**
384
     * Sort the entries by value.
385
     *
386
     * @param int $sort_flags [optional] <p>
387
     *                        You may modify the behavior of the sort using the optional
388
     *                        parameter sort_flags, for details
389
     *                        see sort.
390
     *                        </p>
391
     *
392
     * @return $this
393
     *               <p>(Mutable) Return this Arrayy object.</p>
394
     *
395
     * @phpstan-return static
396
     */
397
    #[\ReturnTypeWillChange]
398
    public function asort(int $sort_flags = 0): self
399
    {
400
        $this->generatorToArray();
28✔
401

402
        \asort($this->array, $sort_flags);
28✔
403

404
        return $this;
28✔
405
    }
406

407
    /**
408
     * Sort the entries by value.
409
     *
410
     * @param int $sort_flags [optional] <p>
411
     *                        You may modify the behavior of the sort using the optional
412
     *                        parameter sort_flags, for details
413
     *                        see sort.
414
     *                        </p>
415
     *
416
     * @return $this
417
     *               <p>(Immutable) Return this Arrayy object.</p>
418
     *
419
     * @phpstan-return static
420
     * @psalm-mutation-free
421
     */
422
    public function asortImmutable(int $sort_flags = 0): self
423
    {
424
        $that = clone $this;
28✔
425

426
        /**
427
         * @psalm-suppress ImpureMethodCall - object is already cloned
428
         */
429
        $that->asort($sort_flags);
28✔
430

431
        return $that;
28✔
432
    }
433

434
    /**
435
     * Counts all elements in an array, or something in an object.
436
     *
437
     * EXAMPLE: <code>
438
     * a([-9, -8, -7, 1.32])->count(); // 4
439
     * </code>
440
     *
441
     * <p>
442
     * For objects, if you have SPL installed, you can hook into count() by implementing interface {@see Countable}.
443
     * The interface has exactly one method, {@see Countable::count()}, which returns the return value for the count()
444
     * function. Please see the {@see Array} section of the manual for a detailed explanation of how arrays are
445
     * implemented and used in PHP.
446
     * </p>
447
     *
448
     * @see http://php.net/manual/en/function.count.php
449
     *
450
     * @param int $mode [optional] If the optional mode parameter is set to
451
     *                  COUNT_RECURSIVE (or 1), count
452
     *                  will recursively count the array. This is particularly useful for
453
     *                  counting all the elements of a multidimensional array. count does not detect infinite recursion.
454
     *
455
     * @return int
456
     *             <p>
457
     *             The number of elements in var, which is
458
     *             typically an array, since anything else will have one
459
     *             element.
460
     *             </p>
461
     *             <p>
462
     *             If var is not an array or an object with
463
     *             implemented Countable interface,
464
     *             1 will be returned.
465
     *             There is one exception, if var is &null;,
466
     *             0 will be returned.
467
     *             </p>
468
     *             <p>
469
     *             Caution: count may return 0 for a variable that isn't set,
470
     *             but it may also return 0 for a variable that has been initialized with an
471
     *             empty array. Use isset to test if a variable is set.
472
     *             </p>
473
     * @psalm-mutation-free
474
     */
475
    public function count(int $mode = \COUNT_NORMAL): int
476
    {
477
        if ($mode !== \COUNT_NORMAL && $mode !== \COUNT_RECURSIVE) {
1,050✔
478
            throw new \ValueError('count(): Argument #2 ($mode) must be either COUNT_NORMAL or COUNT_RECURSIVE');
×
479
        }
480

481
        if (
482
            $this->generator
1,050✔
483
            &&
484
            $mode === \COUNT_NORMAL
1,050✔
485
        ) {
486
            return \iterator_count($this->generator);
28✔
487
        }
488

489
        return \count($this->toArray(), $mode);
1,022✔
490
    }
491

492
    /**
493
     * Exchange the array for another one.
494
     *
495
     * @param array|mixed|static $data
496
     *
497
     * 1. use the current array, if it's a array
498
     * 2. fallback to empty array, if there is nothing
499
     * 3. call "getArray()" on object, if there is a "Arrayy"-object
500
     * 4. call "createFromObject()" on object, if there is a "\Traversable"-object
501
     * 5. call "__toArray()" on object, if the method exists
502
     * 6. cast a string or object with "__toString()" into an array
503
     * 7. throw a "InvalidArgumentException"-Exception
504
     *
505
     * @return array
506
     *
507
     * @phpstan-param  T|array<TKey,T>|self<TKey,T,TData> $data
508
     * @phpstan-return array<TKey,T>
509
     */
510
    public function exchangeArray($data): array
511
    {
512
        /** @phpstan-var array<TKey,T> array */
513
        $array = $this->fallbackForArray($data);
7✔
514

515
        $this->array = $array;
7✔
516
        $this->generator = null;
7✔
517

518
        return $this->array;
7✔
519
    }
520

521
    /**
522
     * Creates a copy of the ArrayyObject.
523
     *
524
     * @return array
525
     *
526
     * @phpstan-return array<int|string|TKey,T>
527
     */
528
    public function getArrayCopy(): array
529
    {
530
        $this->generatorToArray();
42✔
531

532
        return $this->array;
42✔
533
    }
534

535
    /**
536
     * Returns a new iterator, thus implementing the \Iterator interface.
537
     *
538
     * EXAMPLE: <code>
539
     * a(['foo', 'bar'])->getIterator(); // ArrayyIterator['foo', 'bar']
540
     * </code>
541
     *
542
     * @return \Iterator<mixed, mixed>
543
     *                          <p>An iterator for the values in the array.</p>
544
     * @phpstan-return \Iterator<TKey, T>
545
     */
546
    public function getIterator(): \Iterator
547
    {
548
        if ($this->generator instanceof ArrayyRewindableGenerator) {
245✔
549
            $generator = clone $this->generator;
14✔
550

551
            /** @phpstan-var \Arrayy\ArrayyRewindableGenerator<TKey,T> */
552
            $generatorTmp = new ArrayyRewindableExtendedGenerator(
14✔
553
                static function () use ($generator): \Generator {
14✔
554
                    yield from $generator;
14✔
555
                },
14✔
556
                null,
14✔
557
                static::class
14✔
558
            );
14✔
559

560
            $this->generator = $generatorTmp;
14✔
561

562
            return $this->generator;
14✔
563
        }
564

565
        $iterator = $this->getIteratorClass();
238✔
566

567
        if ($iterator === ArrayyIterator::class) {
238✔
568
            return new $iterator($this->toArray(), 0, static::class);
238✔
569
        }
570

571
        $return = new $iterator($this->toArray());
×
572
        \assert($return instanceof \Iterator);
573

574
        return $return;
×
575
    }
576

577
    /**
578
     * Gets the iterator classname for the ArrayObject.
579
     *
580
     * @return string
581
     *
582
     * @phpstan-return class-string
583
     */
584
    public function getIteratorClass(): string
585
    {
586
        return $this->iteratorClass;
238✔
587
    }
588

589
    /**
590
     * Sort the entries by key.
591
     *
592
     * @param int $sort_flags [optional] <p>
593
     *                        You may modify the behavior of the sort using the optional
594
     *                        parameter sort_flags, for details
595
     *                        see sort.
596
     *                        </p>
597
     *
598
     * @return $this
599
     *               <p>(Mutable) Return this Arrayy object.</p>
600
     *
601
     * @phpstan-return static
602
     */
603
    #[\ReturnTypeWillChange]
604
    public function ksort(int $sort_flags = 0): self
605
    {
606
        $this->generatorToArray();
28✔
607

608
        \ksort($this->array, $sort_flags);
28✔
609

610
        return $this;
28✔
611
    }
612

613
    /**
614
     * Sort the entries by key.
615
     *
616
     * @param int $sort_flags [optional] <p>
617
     *                        You may modify the behavior of the sort using the optional
618
     *                        parameter sort_flags, for details
619
     *                        see sort.
620
     *                        </p>
621
     *
622
     * @return $this
623
     *               <p>(Immutable) Return this Arrayy object.</p>
624
     *
625
     * @phpstan-return static
626
     */
627
    public function ksortImmutable(int $sort_flags = 0): self
628
    {
629
        $that = clone $this;
28✔
630

631
        /**
632
         * @psalm-suppress ImpureMethodCall - object is already cloned
633
         */
634
        $that->ksort($sort_flags);
28✔
635

636
        return $that;
28✔
637
    }
638

639
    /**
640
     * Sort an array using a case insensitive "natural order" algorithm.
641
     *
642
     * @return $this
643
     *               <p>(Mutable) Return this Arrayy object.</p>
644
     *
645
     * @phpstan-return static
646
     */
647
    #[\ReturnTypeWillChange]
648
    public function natcasesort(): self
649
    {
650
        $this->generatorToArray();
56✔
651

652
        \natcasesort($this->array);
56✔
653

654
        return $this;
56✔
655
    }
656

657
    /**
658
     * Sort an array using a case insensitive "natural order" algorithm.
659
     *
660
     * @return $this
661
     *               <p>(Immutable) Return this Arrayy object.</p>
662
     *
663
     * @phpstan-return static
664
     * @psalm-mutation-free
665
     */
666
    public function natcasesortImmutable(): self
667
    {
668
        $that = clone $this;
28✔
669

670
        /**
671
         * @psalm-suppress ImpureMethodCall - object is already cloned
672
         */
673
        $that->natcasesort();
28✔
674

675
        return $that;
28✔
676
    }
677

678
    /**
679
     * Sort entries using a "natural order" algorithm.
680
     *
681
     * @return $this
682
     *               <p>(Mutable) Return this Arrayy object.</p>
683
     *
684
     * @phpstan-return static
685
     */
686
    #[\ReturnTypeWillChange]
687
    public function natsort(): self
688
    {
689
        $this->generatorToArray();
70✔
690

691
        \natsort($this->array);
70✔
692

693
        return $this;
70✔
694
    }
695

696
    /**
697
     * Sort entries using a "natural order" algorithm.
698
     *
699
     * @return $this
700
     *               <p>(Immutable) Return this Arrayy object.</p>
701
     *
702
     * @phpstan-return static
703
     * @psalm-mutation-free
704
     */
705
    public function natsortImmutable(): self
706
    {
707
        $that = clone $this;
28✔
708

709
        /**
710
         * @psalm-suppress ImpureMethodCall - object is already cloned
711
         */
712
        $that->natsort();
28✔
713

714
        return $that;
28✔
715
    }
716

717
    /**
718
     * Whether or not an offset exists.
719
     *
720
     * @param bool|int|string $offset
721
     *
722
     * @return bool
723
     *
724
     * @psalm-mutation-free
725
     */
726
    #[\ReturnTypeWillChange]
727
    public function offsetExists($offset): bool
728
    {
729
        // php cast "bool"-index into "int"-index
730
        if ((bool) $offset === $offset) {
1,384✔
731
            $offset = (int) $offset;
7✔
732
        }
733
        \assert(\is_int($offset) || \is_string($offset));
734

735
        $offsetExists = $this->keyExists($offset);
1,384✔
736
        if ($offsetExists === true) {
1,384✔
737
            return true;
1,237✔
738
        }
739

740
        /**
741
         * https://github.com/vimeo/psalm/issues/2536
742
         *
743
         * @psalm-suppress PossiblyInvalidArgument
744
         * @psalm-suppress InvalidScalarArgument
745
         */
746
        if (
747
            $this->pathSeparator
973✔
748
            &&
749
            (string) $offset === $offset
973✔
750
            &&
751
            \strpos($offset, $this->pathSeparator) !== false
973✔
752
        ) {
753
            $explodedPath = \explode($this->pathSeparator, (string) $offset);
28✔
754
            /** @var string $lastOffset - helper for phpstan */
755
            $lastOffset = \array_pop($explodedPath);
28✔
756
            $containerPath = \implode($this->pathSeparator, $explodedPath);
28✔
757

758
            /**
759
             * @psalm-suppress MissingClosureReturnType
760
             * @psalm-suppress MissingClosureParamType
761
             */
762
            $this->callAtPath(
28✔
763
                $containerPath,
28✔
764
                static function ($container) use ($lastOffset, &$offsetExists) {
28✔
765
                    $offsetExists = \array_key_exists($lastOffset, $container);
28✔
766
                }
28✔
767
            );
28✔
768
        }
769

770
        return $offsetExists;
973✔
771
    }
772

773
    /**
774
     * Returns the value at specified offset.
775
     *
776
     * @param int|string $offset
777
     *
778
     * @return mixed
779
     *               <p>Will return null if the offset did not exists.</p>
780
     *
781
     * @template TOffset of key-of<TData>
782
     * @phpstan-param TOffset $offset
783
     * @phpstan-return TData[TOffset]|null
784
     */
785
    #[\ReturnTypeWillChange]
786
    public function &offsetGet($offset)
787
    {
788
        // init
789
        $value = null;
1,167✔
790

791
        if ($this->offsetExists($offset)) {
1,167✔
792
            /* @phpstan-ignore argument.type, argument.templateType */
793
            $value = &$this->__get($offset);
1,153✔
794
        }
795

796
        /* @phpstan-ignore return.type */
797
        return $value;
1,167✔
798
    }
799

800
    /**
801
     * Assigns a value to the specified offset + check the type.
802
     *
803
     * @param int|string|null $offset
804
     * @param mixed           $value
805
     *
806
     * @return void
807
     */
808
    #[\ReturnTypeWillChange]
809
    public function offsetSet($offset, $value)
810
    {
811
        $this->generatorToArray();
301✔
812

813
        if ($offset === null) {
301✔
814
            if ($this->properties !== []) {
63✔
815
                $this->checkType(null, $value);
28✔
816
            }
817

818
            $this->array[] = $value;
56✔
819
        } else {
820
            $this->internalSet(
245✔
821
                $offset,
245✔
822
                $value,
245✔
823
                true
245✔
824
            );
245✔
825
        }
826
    }
827

828
    /**
829
     * Unset an offset.
830
     *
831
     * @param int|string $offset
832
     *
833
     * @return void
834
     *              <p>(Mutable) Return nothing.</p>
835
     */
836
    #[\ReturnTypeWillChange]
837
    public function offsetUnset($offset)
838
    {
839
        $this->generatorToArray();
182✔
840

841
        if ($this->array === []) {
182✔
842
            return;
42✔
843
        }
844

845
        if ($this->keyExists($offset)) {
147✔
846
            unset($this->array[$offset]);
98✔
847

848
            return;
98✔
849
        }
850

851
        /**
852
         * https://github.com/vimeo/psalm/issues/2536
853
         *
854
         * @psalm-suppress PossiblyInvalidArgument
855
         * @psalm-suppress InvalidScalarArgument
856
         */
857
        if (
858
            $this->pathSeparator
70✔
859
            &&
860
            (string) $offset === $offset
70✔
861
            &&
862
            \strpos($offset, $this->pathSeparator) !== false
70✔
863
        ) {
864
            $path = \explode($this->pathSeparator, (string) $offset);
49✔
865
            $pathToUnset = \array_pop($path);
49✔
866

867
            /**
868
             * @psalm-suppress MissingClosureReturnType
869
             * @psalm-suppress MissingClosureParamType
870
             */
871
            $this->callAtPath(
49✔
872
                \implode($this->pathSeparator, $path),
49✔
873
                static function (&$offset) use ($pathToUnset) {
49✔
874
                    if (\is_array($offset)) {
42✔
875
                        unset($offset[$pathToUnset]);
35✔
876
                    } else {
877
                        $offset = null;
7✔
878
                    }
879
                }
49✔
880
            );
49✔
881
        }
882

883
        unset($this->array[$offset]);
70✔
884
    }
885

886
    /**
887
     * Serialize the current "Arrayy"-object.
888
     *
889
     * EXAMPLE: <code>
890
     * a([1, 4, 7])->serialize();
891
     * </code>
892
     *
893
     * @return string
894
     */
895
    public function serialize(): string
896
    {
897
        $this->generatorToArray();
7✔
898

899
        return \serialize($this);
7✔
900
    }
901

902
    /**
903
     * Sets the iterator classname for the current "Arrayy"-object.
904
     *
905
     * @param string $iteratorClass
906
     *
907
     * @throws \InvalidArgumentException
908
     *
909
     * @return void
910
     *
911
     * @phpstan-param class-string<\Arrayy\ArrayyIterator<TKey,T>> $iteratorClass
912
     */
913
    #[\ReturnTypeWillChange]
914
    public function setIteratorClass($iteratorClass)
915
    {
916
        if (\class_exists($iteratorClass)) {
8,972✔
917
            $this->iteratorClass = $iteratorClass;
8,972✔
918

919
            return;
8,972✔
920
        }
921

922
        if (\strpos($iteratorClass, '\\') === 0) {
×
923
            /** @var class-string<\Arrayy\ArrayyIterator<TKey,T>> $iteratorClass */
924
            $iteratorClass = '\\' . $iteratorClass;
×
925
            if (\class_exists($iteratorClass)) {
×
926
                /**
927
                 * @psalm-suppress PropertyTypeCoercion
928
                 */
929
                $this->iteratorClass = $iteratorClass;
×
930

931
                return;
×
932
            }
933
        }
934

935
        throw new \InvalidArgumentException('The iterator class does not exist: ' . $iteratorClass);
×
936
    }
937

938
    /**
939
     * Sort the entries with a user-defined comparison function and maintain key association.
940
     *
941
     * @param callable $callable
942
     *
943
     *@throws \InvalidArgumentException
944
     *
945
     * @return $this
946
     *               <p>(Mutable) Return this Arrayy object.</p>
947
     *
948
     * @phpstan-param  callable(T,T):int $callable
949
     * @phpstan-return static
950
     */
951
    #[\ReturnTypeWillChange]
952
    public function uasort($callable): self
953
    {
954
        $this->generatorToArray();
56✔
955

956
        \uasort($this->array, $callable);
56✔
957

958
        return $this;
56✔
959
    }
960

961
    /**
962
     * Sort the entries with a user-defined comparison function and maintain key association.
963
     *
964
     * @param callable $callable
965
     *
966
     *@throws \InvalidArgumentException
967
     *
968
     * @return $this
969
     *               <p>(Immutable) Return this Arrayy object.</p>
970
     *
971
     * @phpstan-param  callable(T,T):int $callable
972
     * @phpstan-return static
973
     * @psalm-mutation-free
974
     */
975
    public function uasortImmutable($callable): self
976
    {
977
        $that = clone $this;
28✔
978

979
        /**
980
         * @psalm-suppress ImpureMethodCall - object is already cloned
981
         */
982
        $that->uasort($callable);
28✔
983

984
        return $that;
28✔
985
    }
986

987
    /**
988
     * Sort the entries by keys using a user-defined comparison function.
989
     *
990
     * @param callable $callable
991
     *
992
     * @throws \InvalidArgumentException
993
     *
994
     * @return static
995
     *                <p>(Mutable) Return this Arrayy object.</p>
996
     *
997
     * @phpstan-param  callable(TKey,TKey):int $callable
998
     * @phpstan-return static
999
     */
1000
    #[\ReturnTypeWillChange]
1001
    public function uksort($callable): self
1002
    {
1003
        return $this->customSortKeys($callable);
35✔
1004
    }
1005

1006
    /**
1007
     * Sort the entries by keys using a user-defined comparison function.
1008
     *
1009
     * @param callable $callable
1010
     *
1011
     * @throws \InvalidArgumentException
1012
     *
1013
     * @return static
1014
     *                <p>(Immutable) Return this Arrayy object.</p>
1015
     *
1016
     * @phpstan-param  callable(TKey,TKey):int $callable
1017
     * @phpstan-return static
1018
     * @psalm-mutation-free
1019
     */
1020
    public function uksortImmutable($callable): self
1021
    {
1022
        return $this->customSortKeysImmutable($callable);
7✔
1023
    }
1024

1025
    /**
1026
     * Unserialize an string and return the instance of the "Arrayy"-class.
1027
     *
1028
     * EXAMPLE: <code>
1029
     * $serialized = a([1, 4, 7])->serialize();
1030
     * a()->unserialize($serialized);
1031
     * </code>
1032
     *
1033
     * @param string $string
1034
     *
1035
     * @return $this
1036
     *
1037
     * @phpstan-return static
1038
     */
1039
    #[\ReturnTypeWillChange]
1040
    public function unserialize($string): self
1041
    {
1042
        return \unserialize($string, ['allowed_classes' => [__CLASS__, TypeCheckPhpDoc::class]]);
7✔
1043
    }
1044

1045
    /**
1046
     * Append a (key) + values to the current array.
1047
     *
1048
     * EXAMPLE: <code>
1049
     * a(['fΓ²Γ΄' => ['bΓ Ε™']])->appendArrayValues(['foo1', 'foo2'], 'fΓ²Γ΄'); // Arrayy['fΓ²Γ΄' => ['bΓ Ε™', 'foo1', 'foo2']]
1050
     * </code>
1051
     *
1052
     * @param array $values
1053
     * @param mixed $key
1054
     *
1055
     * @return $this
1056
     *               <p>(Mutable) Return this Arrayy object, with the appended values.</p>
1057
     *
1058
     * @phpstan-param array<T> $values
1059
     * @phpstan-param  TKey|null $key
1060
     * @phpstan-return static
1061
     */
1062
    public function appendArrayValues(array $values, $key = null)
1063
    {
1064
        $this->generatorToArray();
7✔
1065

1066
        if ($key !== null) {
7✔
1067
            if (
1068
                isset($this->array[$key])
7✔
1069
                &&
1070
                \is_array($this->array[$key])
7✔
1071
            ) {
1072
                foreach ($values as $value) {
7✔
1073
                    /* @phpstan-ignore assign.propertyType */
1074
                    $this->array[$key][] = $value;
7✔
1075
                }
1076
            } else {
1077
                foreach ($values as $value) {
3✔
1078
                    $this->array[$key] = $value;
×
1079
                }
1080
            }
1081
        } else {
1082
            foreach ($values as $value) {
×
1083
                $this->array[] = $value;
×
1084
            }
1085
        }
1086

1087
        return $this;
7✔
1088
    }
1089

1090
    /**
1091
     * Add a suffix to each key.
1092
     *
1093
     * @param int|string $prefix
1094
     *
1095
     * @return static
1096
     *                <p>(Immutable) Return an Arrayy object, with the prefixed keys.</p>
1097
     *
1098
     * @phpstan-return static
1099
     * @psalm-mutation-free
1100
     */
1101
    public function appendToEachKey($prefix): self
1102
    {
1103
        // init
1104
        $result = [];
70✔
1105

1106
        foreach ($this->getGenerator() as $key => $item) {
70✔
1107
            if ($item instanceof self) {
63✔
1108
                $result[$prefix . $key] = $item->appendToEachKey($prefix);
×
1109
            } elseif (\is_array($item)) {
63✔
1110
                $result[$prefix . $key] = self::create($item, $this->iteratorClass, false)
×
1111
                    ->appendToEachKey($prefix)
×
1112
                    ->toArray();
×
1113
            } else {
1114
                $result[$prefix . $key] = $item;
63✔
1115
            }
1116
        }
1117

1118
        return self::create(
70✔
1119
            $result,
70✔
1120
            $this->iteratorClass,
70✔
1121
            false
70✔
1122
        );
70✔
1123
    }
1124

1125
    /**
1126
     * Add a prefix to each value.
1127
     *
1128
     * @param float|int|string $prefix
1129
     *
1130
     * @return static
1131
     *                <p>(Immutable) Return an Arrayy object, with the prefixed values.</p>
1132
     *
1133
     * @phpstan-return static
1134
     * @psalm-mutation-free
1135
     */
1136
    public function appendToEachValue($prefix): self
1137
    {
1138
        // init
1139
        $result = [];
70✔
1140

1141
        foreach ($this->getGenerator() as $key => $item) {
70✔
1142
            if ($item instanceof self) {
63✔
1143
                $result[$key] = $item->appendToEachValue($prefix);
×
1144
            } elseif (\is_array($item)) {
63✔
1145
                $result[$key] = self::create($item, $this->iteratorClass, false)->appendToEachValue($prefix)->toArray();
×
1146
            } elseif (\is_object($item) === true) {
63✔
1147
                $result[$key] = $item;
7✔
1148
            } else {
1149
                $result[$key] = $prefix . $item;
56✔
1150
            }
1151
        }
1152

1153
        return self::create($result, $this->iteratorClass, false);
70✔
1154
    }
1155

1156
    /**
1157
     * Sort an array in reverse order and maintain index association.
1158
     *
1159
     * @return $this
1160
     *               <p>(Mutable) Return this Arrayy object.</p>
1161
     *
1162
     * @phpstan-return static
1163
     */
1164
    public function arsort(): self
1165
    {
1166
        $this->generatorToArray();
28✔
1167

1168
        \arsort($this->array);
28✔
1169

1170
        return $this;
28✔
1171
    }
1172

1173
    /**
1174
     * Sort an array in reverse order and maintain index association.
1175
     *
1176
     * @return $this
1177
     *               <p>(Immutable) Return this Arrayy object.</p>
1178
     *
1179
     * @phpstan-return static
1180
     * @psalm-mutation-free
1181
     */
1182
    public function arsortImmutable(): self
1183
    {
1184
        $that = clone $this;
70✔
1185

1186
        $that->generatorToArray();
70✔
1187

1188
        \arsort($that->array);
70✔
1189

1190
        return $that;
70✔
1191
    }
1192

1193
    /**
1194
     * Iterate over the current array and execute a callback for each loop.
1195
     *
1196
     * EXAMPLE: <code>
1197
     * $result = A::create();
1198
     * $closure = function ($value, $key) use ($result) {
1199
     *     $result[$key] = ':' . $value . ':';
1200
     * };
1201
     * a(['foo', 'bar' => 'bis'])->at($closure); // Arrayy[':foo:', 'bar' => ':bis:']
1202
     * </code>
1203
     *
1204
     * @param \Closure $closure
1205
     *
1206
     * @return static
1207
     *                <p>(Immutable)</p>
1208
     *
1209
     * @phpstan-param \Closure(T,TKey):mixed $closure <p>INFO: \Closure result is not used, but void is not supported in PHP 7.0</p>
1210
     * @phpstan-return static
1211
     * @psalm-mutation-free
1212
     */
1213
    public function at(\Closure $closure): self
1214
    {
1215
        $that = clone $this;
21✔
1216

1217
        foreach ($that->getGenerator() as $key => $value) {
21✔
1218
            $closure($value, $key);
21✔
1219
        }
1220

1221
        return static::create(
21✔
1222
            $that->toArray(),
21✔
1223
            $this->iteratorClass,
21✔
1224
            false
21✔
1225
        );
21✔
1226
    }
1227

1228
    /**
1229
     * Returns the average value of the current array.
1230
     *
1231
     * EXAMPLE: <code>
1232
     * a([-9, -8, -7, 1.32])->average(2); // -5.67
1233
     * </code>
1234
     *
1235
     * @param int $decimals <p>The number of decimal-numbers to return.</p>
1236
     *
1237
     * @return float|int
1238
     *                   <p>The average value.</p>
1239
     * @psalm-mutation-free
1240
     */
1241
    public function average($decimals = 0)
1242
    {
1243
        $array = $this->toArray();
70✔
1244
        $count = \count($array, \COUNT_NORMAL);
70✔
1245

1246
        if (!$count) {
70✔
1247
            return 0;
14✔
1248
        }
1249

1250
        if ((int) $decimals !== $decimals) {
56✔
1251
            $decimals = 0;
21✔
1252
        }
1253

1254
        $sum = 0;
56✔
1255
        foreach ($array as $value) {
56✔
1256
            if (
1257
                \is_int($value)
56✔
1258
                ||
1259
                \is_float($value)
49✔
1260
                ||
1261
                \is_bool($value)
56✔
1262
            ) {
1263
                $sum += $value;
42✔
1264
            } elseif (\is_string($value) && \is_numeric($value)) {
14✔
1265
                $sum += (float) $value;
×
1266
            }
1267
        }
1268

1269
        return \round($sum / $count, $decimals);
56✔
1270
    }
1271

1272
    /**
1273
     * Changes all keys in an array.
1274
     *
1275
     * @param int $case [optional] <p> Either <strong>CASE_UPPER</strong><br />
1276
     *                  or <strong>CASE_LOWER</strong> (default)</p>
1277
     *
1278
     * @return static
1279
     *                <p>(Immutable)</p>
1280
     *
1281
     * @phpstan-return static
1282
     * @psalm-mutation-free
1283
     */
1284
    public function changeKeyCase(int $case = \CASE_LOWER): self
1285
    {
1286
        if (
1287
            $case !== \CASE_LOWER
7✔
1288
            &&
1289
            $case !== \CASE_UPPER
7✔
1290
        ) {
1291
            $case = \CASE_LOWER;
×
1292
        }
1293

1294
        $return = [];
7✔
1295
        foreach ($this->getGenerator() as $key => $value) {
7✔
1296
            if ($case === \CASE_LOWER) {
7✔
1297
                $key = \mb_convert_case(
7✔
1298
                    (string) $key,
7✔
1299
                    \defined('MB_CASE_LOWER_SIMPLE') ? \MB_CASE_LOWER_SIMPLE : \MB_CASE_LOWER,
7✔
1300
                    'UTF-8'
7✔
1301
                );
7✔
1302
            } else {
1303
                $key = \mb_convert_case(
7✔
1304
                    (string) $key,
7✔
1305
                    \defined('MB_CASE_UPPER_SIMPLE') ? \MB_CASE_UPPER_SIMPLE : \MB_CASE_UPPER,
7✔
1306
                    'UTF-8'
7✔
1307
                );
7✔
1308
            }
1309

1310
            $return[$key] = $value;
7✔
1311
        }
1312

1313
        return static::create(
7✔
1314
            $return,
7✔
1315
            $this->iteratorClass,
7✔
1316
            false
7✔
1317
        );
7✔
1318
    }
1319

1320
    /**
1321
     * Change the path separator of the array wrapper.
1322
     *
1323
     * By default, the separator is: "."
1324
     *
1325
     * @param non-empty-string $separator <p>Separator to set.</p>
1326
     *
1327
     * @return $this
1328
     *               <p>(Mutable) Return this Arrayy object.</p>
1329
     *
1330
     * @phpstan-return static
1331
     */
1332
    public function changeSeparator($separator): self
1333
    {
1334
        $this->pathSeparator = $separator;
77✔
1335

1336
        return $this;
77✔
1337
    }
1338

1339
    /**
1340
     * Create a chunked version of the current array.
1341
     *
1342
     * EXAMPLE: <code>
1343
     * a([-9, -8, -7, 1.32])->chunk(2); // Arrayy[[-9, -8], [-7, 1.32]]
1344
     * </code>
1345
     *
1346
     * @param int  $size         <p>Size of each chunk.</p>
1347
     * @param bool $preserveKeys <p>Whether array keys are preserved or no.</p>
1348
     *
1349
     * @return static|static[]
1350
     *                <p>(Immutable) A new array of chunks from the original array.</p>
1351
     *
1352
     * @phpstan-return static
1353
     * @psalm-mutation-free
1354
     */
1355
    public function chunk($size, $preserveKeys = false): self
1356
    {
1357
        if ($preserveKeys) {
42✔
1358
            $generator = function () use ($size) {
×
1359
                $values = [];
×
1360
                $tmpCounter = 0;
×
1361
                foreach ($this->getGenerator() as $key => $value) {
×
1362
                    ++$tmpCounter;
×
1363

1364
                    $values[$key] = $value;
×
1365
                    if ($tmpCounter === $size) {
×
1366
                        yield $values;
×
1367

1368
                        $values = [];
×
1369
                        $tmpCounter = 0;
×
1370
                    }
1371
                }
1372

1373
                if ($values !== []) {
×
1374
                    yield $values;
×
1375
                }
1376
            };
×
1377
        } else {
1378
            $generator = function () use ($size) {
42✔
1379
                $values = [];
42✔
1380
                $tmpCounter = 0;
42✔
1381
                foreach ($this->getGenerator() as $value) {
42✔
1382
                    ++$tmpCounter;
42✔
1383

1384
                    $values[] = $value;
42✔
1385
                    if ($tmpCounter === $size) {
42✔
1386
                        yield $values;
42✔
1387

1388
                        $values = [];
42✔
1389
                        $tmpCounter = 0;
42✔
1390
                    }
1391
                }
1392

1393
                if ($values !== []) {
42✔
1394
                    yield $values;
35✔
1395
                }
1396
            };
42✔
1397
        }
1398

1399
        return static::create(
42✔
1400
            $generator,
42✔
1401
            $this->iteratorClass,
42✔
1402
            false
42✔
1403
        );
42✔
1404
    }
1405

1406
    /**
1407
     * Clean all falsy values from the current array.
1408
     *
1409
     * EXAMPLE: <code>
1410
     * a([-8 => -9, 1, 2 => false])->clean(); // Arrayy[-8 => -9, 1]
1411
     * </code>
1412
     *
1413
     * @return static
1414
     *                <p>(Immutable)</p>
1415
     *
1416
     * @phpstan-return static
1417
     * @psalm-mutation-free
1418
     */
1419
    public function clean(): self
1420
    {
1421
        return $this->filter(
56✔
1422
            static function ($value) {
56✔
1423
                return (bool) $value;
49✔
1424
            }
56✔
1425
        );
56✔
1426
    }
1427

1428
    /**
1429
     * WARNING!!! -> Clear the current full array or a $key of it.
1430
     *
1431
     * EXAMPLE: <code>
1432
     * a([-8 => -9, 1, 2 => false])->clear(); // Arrayy[]
1433
     * </code>
1434
     *
1435
     * @param int|int[]|string|string[]|null $key
1436
     *
1437
     * @return $this
1438
     *               <p>(Mutable) Return this Arrayy object, with an empty array.</p>
1439
     *
1440
     * @phpstan-return static
1441
     */
1442
    public function clear($key = null): self
1443
    {
1444
        if ($key !== null) {
70✔
1445
            if (\is_array($key)) {
21✔
1446
                foreach ($key as $keyTmp) {
7✔
1447
                    $this->offsetUnset($keyTmp);
7✔
1448
                }
1449
            } else {
1450
                $this->offsetUnset($key);
14✔
1451
            }
1452

1453
            return $this;
21✔
1454
        }
1455

1456
        $this->array = [];
49✔
1457
        $this->generator = null;
49✔
1458

1459
        return $this;
49✔
1460
    }
1461

1462
    /**
1463
     * Check if an item is in the current array.
1464
     *
1465
     * EXAMPLE: <code>
1466
     * a([1, true])->containsOnly(true); // false
1467
     * </code>
1468
     *
1469
     * @param float|int|string $value
1470
     * @param bool             $recursive
1471
     * @param bool             $strict
1472
     *
1473
     * @return bool
1474
     * @psalm-mutation-free
1475
     */
1476
    public function containsOnly($value, bool $recursive = false, bool $strict = true): bool
1477
    {
1478
        if ($recursive === true) {
70✔
1479
            return $this->in_array_recursive($value, $this->toArray(), $strict);
×
1480
        }
1481

1482
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1483
        $tmpCount = 0;
70✔
1484
        foreach ($this->getGeneratorByReference() as &$valueFromArray) {
70✔
1485
            $tmpCount++;
56✔
1486

1487
            if ($strict) {
56✔
1488
                if ($value !== $valueFromArray) {
56✔
1489
                    return false;
36✔
1490
                }
1491
            } else {
1492
                /** @noinspection NestedPositiveIfStatementsInspection */
1493
                if ($value != $valueFromArray) {
×
1494
                    return false;
×
1495
                }
1496
            }
1497
        }
1498

1499
        return $tmpCount !== 0;
49✔
1500
    }
1501

1502
    /**
1503
     * Check if an item is in the current array.
1504
     *
1505
     * EXAMPLE: <code>
1506
     * a([1, true])->contains(true); // true
1507
     * </code>
1508
     *
1509
     * @param float|int|string $value
1510
     * @param bool             $recursive
1511
     * @param bool             $strict
1512
     *
1513
     * @return bool
1514
     * @psalm-mutation-free
1515
     */
1516
    public function contains($value, bool $recursive = false, bool $strict = true): bool
1517
    {
1518
        if ($recursive === true) {
164✔
1519
            return $this->in_array_recursive($value, $this->toArray(), $strict);
129✔
1520
        }
1521

1522
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1523
        foreach ($this->getGeneratorByReference() as &$valueFromArray) {
98✔
1524
            if ($strict) {
77✔
1525
                if ($value === $valueFromArray) {
77✔
1526
                    return true;
65✔
1527
                }
1528
            } else {
1529
                /** @noinspection NestedPositiveIfStatementsInspection */
1530
                if ($value == $valueFromArray) {
×
1531
                    return true;
×
1532
                }
1533
            }
1534
        }
1535

1536
        return false;
49✔
1537
    }
1538

1539
    /**
1540
     * Check if an (case-insensitive) string is in the current array.
1541
     *
1542
     * EXAMPLE: <code>
1543
     * a(['E', 'Γ©'])->containsCaseInsensitive('Γ‰'); // true
1544
     * </code>
1545
     *
1546
     * @param mixed $value
1547
     * @param bool  $recursive
1548
     *
1549
     * @return bool
1550
     * @psalm-mutation-free
1551
     *
1552
     * @psalm-suppress InvalidCast - hack for int|float|bool support
1553
     */
1554
    public function containsCaseInsensitive($value, $recursive = false): bool
1555
    {
1556
        if ($value === null) {
182✔
1557
            return false;
14✔
1558
        }
1559

1560
        if ($recursive === true) {
168✔
1561
            /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1562
            foreach ($this->getGeneratorByReference() as &$valueTmp) {
168✔
1563
                if (\is_array($valueTmp)) {
154✔
1564
                    $return = (new self($valueTmp))->containsCaseInsensitive($value, $recursive);
35✔
1565
                    if ($return === true) {
35✔
1566
                        return $return;
27✔
1567
                    }
1568
                } elseif (\mb_strtoupper((string) $valueTmp) === \mb_strtoupper((string) $value)) {
154✔
1569
                    return true;
112✔
1570
                }
1571
            }
1572

1573
            return false;
56✔
1574
        }
1575

1576
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1577
        foreach ($this->getGeneratorByReference() as &$valueTmp) {
84✔
1578
            if (\mb_strtoupper((string) $valueTmp) === \mb_strtoupper((string) $value)) {
77✔
1579
                return true;
56✔
1580
            }
1581
        }
1582

1583
        return false;
28✔
1584
    }
1585

1586
    /**
1587
     * Check if the given key/index exists in the array.
1588
     *
1589
     * EXAMPLE: <code>
1590
     * a([1 => true])->containsKey(1); // true
1591
     * </code>
1592
     *
1593
     * @param int|string $key <p>key/index to search for</p>
1594
     *
1595
     * @return bool
1596
     *              <p>Returns true if the given key/index exists in the array, false otherwise.</p>
1597
     *
1598
     * @psalm-mutation-free
1599
     */
1600
    public function containsKey($key): bool
1601
    {
1602
        return $this->offsetExists($key);
28✔
1603
    }
1604

1605
    /**
1606
     * Check if all given needles are present in the array as key/index.
1607
     *
1608
     * EXAMPLE: <code>
1609
     * a([1 => true])->containsKeys(array(1 => 0)); // true
1610
     * </code>
1611
     *
1612
     * @param array $needles   <p>The keys you are searching for.</p>
1613
     * @param bool  $recursive
1614
     *
1615
     * @return bool
1616
     *              <p>Returns true if all the given keys/indexes exists in the array, false otherwise.</p>
1617
     *
1618
     * @phpstan-param array<TKey> $needles
1619
     * @psalm-mutation-free
1620
     */
1621
    public function containsKeys(array $needles, bool $recursive = false): bool
1622
    {
1623
        if ($recursive === true) {
14✔
1624
            return
14✔
1625
                \count(
14✔
1626
                    \array_intersect(
14✔
1627
                        $needles,
14✔
1628
                        $this->keys(true)->toArray()
14✔
1629
                    ),
14✔
1630
                    \COUNT_RECURSIVE
14✔
1631
                )
14✔
1632
                ===
14✔
1633
                \count(
14✔
1634
                    $needles,
14✔
1635
                    \COUNT_RECURSIVE
14✔
1636
                );
14✔
1637
        }
1638

1639
        return \count(
7✔
1640
            \array_intersect($needles, $this->keys()->toArray()),
7✔
1641
            \COUNT_NORMAL
7✔
1642
        )
7✔
1643
                ===
7✔
1644
                \count(
7✔
1645
                    $needles,
7✔
1646
                    \COUNT_NORMAL
7✔
1647
                );
7✔
1648
    }
1649

1650
    /**
1651
     * Check if all given needles are present in the array as key/index.
1652
     *
1653
     * @param array $needles <p>The keys you are searching for.</p>
1654
     *
1655
     * @return bool
1656
     *              <p>Returns true if all the given keys/indexes exists in the array, false otherwise.</p>
1657
     *
1658
     * @phpstan-param array<TKey> $needles
1659
     * @psalm-mutation-free
1660
     */
1661
    public function containsKeysRecursive(array $needles): bool
1662
    {
1663
        return $this->containsKeys($needles, true);
7✔
1664
    }
1665

1666
    /**
1667
     * alias: for "Arrayy->contains()"
1668
     *
1669
     * @param float|int|string $value
1670
     *
1671
     * @return bool
1672
     *
1673
     * @see Arrayy::contains()
1674
     * @psalm-mutation-free
1675
     */
1676
    public function containsValue($value): bool
1677
    {
1678
        return $this->contains($value);
63✔
1679
    }
1680

1681
    /**
1682
     * alias: for "Arrayy->contains($value, true)"
1683
     *
1684
     * @param float|int|string $value
1685
     *
1686
     * @return bool
1687
     *
1688
     * @see Arrayy::contains()
1689
     * @psalm-mutation-free
1690
     */
1691
    public function containsValueRecursive($value): bool
1692
    {
1693
        return $this->contains($value, true);
126✔
1694
    }
1695

1696
    /**
1697
     * Check if all given needles are present in the array.
1698
     *
1699
     * EXAMPLE: <code>
1700
     * a([1, true])->containsValues(array(1, true)); // true
1701
     * </code>
1702
     *
1703
     * @param array $needles
1704
     *
1705
     * @return bool
1706
     *              <p>Returns true if all the given values exists in the array, false otherwise.</p>
1707
     *
1708
     * @phpstan-param array<T> $needles
1709
     * @psalm-mutation-free
1710
     */
1711
    public function containsValues(array $needles): bool
1712
    {
1713
        return \count(
7✔
1714
            \array_intersect(
7✔
1715
                $needles,
7✔
1716
                $this->toArray()
7✔
1717
            ),
7✔
1718
            \COUNT_NORMAL
7✔
1719
        )
7✔
1720
               ===
7✔
1721
               \count(
7✔
1722
                   $needles,
7✔
1723
                   \COUNT_NORMAL
7✔
1724
               );
7✔
1725
    }
1726

1727
    /**
1728
     * Counts all the values of an array
1729
     *
1730
     * @see          http://php.net/manual/en/function.array-count-values.php
1731
     *
1732
     * @return static
1733
     *                <p>
1734
     *                (Immutable)
1735
     *                An associative Arrayy-object of values from input as
1736
     *                keys and their count as value.
1737
     *                </p>
1738
     *
1739
     * @phpstan-return static
1740
     * @psalm-mutation-free
1741
     */
1742
    public function countValues(): self
1743
    {
1744
        /** @phpstan-var static $return - help for phpstan */
1745
        $return = self::create(\array_count_values($this->toArray()), $this->iteratorClass);
49✔
1746

1747
        return $return;
49✔
1748
    }
1749

1750
    /**
1751
     * Creates an Arrayy object.
1752
     *
1753
     * @param mixed  $data
1754
     * @param string $iteratorClass
1755
     * @param bool   $checkPropertiesInConstructor
1756
     *
1757
     * @return static
1758
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1759
     *
1760
     * @phpstan-param  TData|self<TKey,T,TData>|\Traversable<TKey,T>|callable|object|scalar|null $data
1761
     * @phpstan-param  class-string<\Arrayy\ArrayyIterator<TKey,T>> $iteratorClass
1762
     * @phpstan-return static
1763
     * @psalm-mutation-free
1764
     */
1765
    public static function create(
1766
        $data = [],
1767
        string $iteratorClass = ArrayyIterator::class,
1768
        bool $checkPropertiesInConstructor = true
1769
    ) {
1770
        /** @var static $instance */
1771
        $instance = new static( // @phpstan-ignore new.static
5,513✔
1772
            $data,
5,513✔
1773
            $iteratorClass,
5,513✔
1774
            $checkPropertiesInConstructor
5,513✔
1775
        );
5,513✔
1776

1777
        return $instance;
5,513✔
1778
    }
1779

1780
    /**
1781
     * Flatten an array with the given character as a key delimiter.
1782
     *
1783
     * EXAMPLE: <code>
1784
     * $dot = a(['foo' => ['abc' => 'xyz', 'bar' => ['baz']]]);
1785
     * $flatten = $dot->flatten();
1786
     * $flatten['foo.abc']; // 'xyz'
1787
     * $flatten['foo.bar.0']; // 'baz'
1788
     * </code>
1789
     *
1790
     * @param string     $delimiter
1791
     * @param string     $prepend
1792
     * @param array|null $items
1793
     *
1794
     * @return array
1795
     *
1796
     * @phpstan-param array<array-key, mixed>|null $items
1797
     * @phpstan-return array<array-key, mixed>
1798
     */
1799
    public function flatten($delimiter = '.', $prepend = '', $items = null)
1800
    {
1801
        // init
1802
        $flatten = [];
14✔
1803

1804
        if ($items === null) {
14✔
1805
            $items = $this->getArray();
14✔
1806
        }
1807

1808
        foreach ($items as $key => $value) {
14✔
1809
            if (\is_array($value) && $value !== []) {
14✔
1810
                $flatten[] = $this->flatten($delimiter, $prepend . $key . $delimiter, $value);
14✔
1811
            } else {
1812
                $flatten[] = [$prepend . $key => $value];
14✔
1813
            }
1814
        }
1815

1816
        if (\count($flatten) === 0) {
14✔
1817
            return [];
×
1818
        }
1819

1820
        return \array_merge_recursive([], ...$flatten);
14✔
1821
    }
1822

1823
    /**
1824
     * WARNING: Creates an Arrayy object by reference.
1825
     *
1826
     * @param array $array
1827
     *
1828
     * @return $this
1829
     *               <p>(Mutable) Return this Arrayy object.</p>
1830
     *
1831
     * @phpstan-param  array<array-key|TKey,T> $array
1832
     * @phpstan-return $this
1833
     *
1834
     * @internal this will not check any types because it's set directly as reference
1835
     */
1836
    public function createByReference(array &$array = []): self
1837
    {
1838
        $this->array = &$array;
199✔
1839
        $this->generator = null;
199✔
1840

1841
        return $this;
199✔
1842
    }
1843

1844
    /**
1845
     * Create an new instance from a callable function which will return an Generator.
1846
     *
1847
     * @param callable $generatorFunction
1848
     *
1849
     * @return static
1850
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1851
     *
1852
     * @phpstan-param callable():\Generator<TKey,T> $generatorFunction
1853
     * @phpstan-return static
1854
     * @psalm-mutation-free
1855
     */
1856
    public static function createFromGeneratorFunction(callable $generatorFunction): self
1857
    {
1858
        return self::create($generatorFunction);
56✔
1859
    }
1860

1861
    /**
1862
     * Create an new instance filled with a copy of values from a "Generator"-object.
1863
     *
1864
     * @param \Generator $generator
1865
     *
1866
     * @return static
1867
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1868
     *
1869
     * @phpstan-param \Generator<TKey,T> $generator
1870
     * @phpstan-return static
1871
     * @psalm-mutation-free
1872
     */
1873
    public static function createFromGeneratorImmutable(\Generator $generator): self
1874
    {
1875
        return self::create(\iterator_to_array($generator, true));
35✔
1876
    }
1877

1878
    /**
1879
     * Create an new Arrayy object via JSON.
1880
     *
1881
     * @param string $json
1882
     *
1883
     * @return static
1884
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1885
     *
1886
     * @phpstan-return static
1887
     * @psalm-mutation-free
1888
     */
1889
    public static function createFromJson(string $json): self
1890
    {
1891
        return static::create(\json_decode($json, true));
42✔
1892
    }
1893

1894
    /**
1895
     * Create an new Arrayy object via JSON.
1896
     *
1897
     * @param array $array
1898
     *
1899
     * @return static
1900
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1901
     *
1902
     * @phpstan-param array<TKey,T> $array
1903
     * @phpstan-return static
1904
     * @psalm-mutation-free
1905
     */
1906
    public static function createFromArray(array $array): self
1907
    {
1908
        return static::create($array);
7✔
1909
    }
1910

1911
    /**
1912
     * Create an new instance filled with values from an object that is iterable.
1913
     *
1914
     * @param \Traversable $object <p>iterable object</p>
1915
     *
1916
     * @return static
1917
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1918
     *
1919
     * @phpstan-param \Traversable<array-key,T> $object
1920
     * @phpstan-return static
1921
     * @psalm-mutation-free
1922
     */
1923
    public static function createFromObject(\Traversable $object): self
1924
    {
1925
        // init
1926
        $arrayy = static::create();
28✔
1927

1928
        if ($object instanceof self) {
28✔
1929
            $objectArray = $object->getGenerator();
28✔
1930
        } else {
1931
            $objectArray = $object;
×
1932
        }
1933

1934
        foreach ($objectArray as $key => $value) {
28✔
1935
            /**
1936
             * @psalm-suppress ImpureMethodCall - object is already re-created
1937
             */
1938
            $arrayy->internalSet($key, $value);
21✔
1939
        }
1940

1941
        return $arrayy;
28✔
1942
    }
1943

1944
    /**
1945
     * Create an new instance filled with values from an object.
1946
     *
1947
     * @param object $object
1948
     *
1949
     * @return static
1950
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1951
     *
1952
     * @phpstan-return static
1953
     * @psalm-mutation-free
1954
     */
1955
    public static function createFromObjectVars($object): self
1956
    {
1957
        return self::create(self::objectToArray($object));
42✔
1958
    }
1959

1960
    /**
1961
     * Create an new Arrayy object via string.
1962
     *
1963
     * @param string                $str       <p>The input string.</p>
1964
     * @param non-empty-string|null $delimiter <p>The boundary string.</p>
1965
     * @param string|null           $regEx     <p>Use the $delimiter or the $regEx, so if $pattern is null, $delimiter will be
1966
     *                                         used.</p>
1967
     *
1968
     * @return static
1969
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1970
     *
1971
     * @phpstan-return static
1972
     * @psalm-mutation-free
1973
     */
1974
    public static function createFromString(string $str, ?string $delimiter = null, ?string $regEx = null): self
1975
    {
1976
        if ($regEx) {
70✔
1977
            \preg_match_all($regEx, $str, $array);
7✔
1978

1979
            if (!empty($array)) {
7✔
1980
                $array = $array[0];
7✔
1981
            }
1982
        } else {
1983
            /** @noinspection NestedPositiveIfStatementsInspection */
1984
            if ($delimiter !== null) {
63✔
1985
                $array = \explode($delimiter, $str);
49✔
1986
            } else {
1987
                $array = [$str];
14✔
1988
            }
1989
        }
1990

1991
        // trim all string in the array
1992
        /**
1993
         * @psalm-suppress MissingClosureParamType
1994
         */
1995
        \array_walk(
70✔
1996
            $array,
70✔
1997
            static function (&$val) {
70✔
1998
                if ((string) $val === $val) {
70✔
1999
                    $val = \trim($val);
70✔
2000
                }
2001
            }
70✔
2002
        );
70✔
2003

2004
        /** @var static $return - help for phpstan */
2005
        $return = static::create($array);
70✔
2006

2007
        return $return;
70✔
2008
    }
2009

2010
    /**
2011
     * Create an new instance filled with a copy of values from a "Traversable"-object.
2012
     *
2013
     * @param \Traversable $traversable
2014
     * @param bool         $use_keys    [optional] <p>
2015
     *                                  Whether to use the iterator element keys as index.
2016
     *                                  </p>
2017
     *
2018
     * @return static
2019
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
2020
     *
2021
     * @phpstan-param \Traversable<array-key|TKey,T> $traversable
2022
     * @phpstan-return static
2023
     * @psalm-mutation-free
2024
     */
2025
    public static function createFromTraversableImmutable(\Traversable $traversable, bool $use_keys = true): self
2026
    {
2027
        return self::create(\iterator_to_array($traversable, $use_keys));
7✔
2028
    }
2029

2030
    /**
2031
     * Create an new instance containing a range of elements.
2032
     *
2033
     * @param float|int|string $low  <p>First value of the sequence.</p>
2034
     * @param float|int|string $high <p>The sequence is ended upon reaching the end value.</p>
2035
     * @param float|int        $step <p>Used as the increment between elements in the sequence.</p>
2036
     *
2037
     * @return static
2038
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
2039
     *
2040
     * @phpstan-return static
2041
     * @psalm-mutation-free
2042
     */
2043
    public static function createWithRange($low, $high, $step = 1): self
2044
    {
2045
        /** @phpstan-var static $return - help for phpstan */
2046
        $return = static::create(\range($low, $high, $step));
14✔
2047

2048
        return $return;
14✔
2049
    }
2050

2051
    /**
2052
     * Gets the element of the array at the current internal iterator position.
2053
     *
2054
     * @return false|mixed
2055
     *
2056
     * @phpstan-return false|T
2057
     */
2058
    public function current()
2059
    {
2060
        if ($this->generator) {
×
2061
            return $this->generator->current();
×
2062
        }
2063

2064
        return \current($this->array);
×
2065
    }
2066

2067
    /**
2068
     * Custom sort by index via "uksort".
2069
     *
2070
     * EXAMPLE: <code>
2071
     * $callable = function ($a, $b) {
2072
     *     if ($a == $b) {
2073
     *         return 0;
2074
     *     }
2075
     *     return ($a > $b) ? 1 : -1;
2076
     * };
2077
     * $arrayy = a(['three' => 3, 'one' => 1, 'two' => 2]);
2078
     * $resultArrayy = $arrayy->customSortKeys($callable); // Arrayy['one' => 1, 'three' => 3, 'two' => 2]
2079
     * </code>
2080
     *
2081
     * @see          http://php.net/manual/en/function.uksort.php
2082
     *
2083
     * @param callable $callable
2084
     *
2085
     * @throws \InvalidArgumentException
2086
     *
2087
     * @return $this
2088
     *               <p>(Mutable) Return this Arrayy object.</p>
2089
     *
2090
     * @phpstan-param  callable(TKey,TKey):int $callable
2091
     * @phpstan-return static
2092
     */
2093
    public function customSortKeys(callable $callable): self
2094
    {
2095
        $this->generatorToArray();
35✔
2096

2097
        /* @phpstan-ignore argument.type (internal keys are array-key|TKey, the callback contract is TKey) */
2098
        \uksort($this->array, $callable);
35✔
2099

2100
        return $this;
35✔
2101
    }
2102

2103
    /**
2104
     * Custom sort by index via "uksort".
2105
     *
2106
     * @see          http://php.net/manual/en/function.uksort.php
2107
     *
2108
     * @param callable $callable
2109
     *
2110
     * @throws \InvalidArgumentException
2111
     *
2112
     * @return $this
2113
     *               <p>(Immutable) Return this Arrayy object.</p>
2114
     *
2115
     * @phpstan-param  callable(TKey,TKey):int $callable
2116
     * @phpstan-return static
2117
     * @psalm-mutation-free
2118
     */
2119
    public function customSortKeysImmutable(callable $callable): self
2120
    {
2121
        $that = clone $this;
7✔
2122

2123
        $that->generatorToArray();
7✔
2124

2125
        /**
2126
         * @psalm-suppress ImpureFunctionCall - object is already cloned
2127
         */
2128
        /* @phpstan-ignore argument.type (internal keys are array-key|TKey, the callback contract is TKey) */
2129
        \uksort($that->array, $callable);
7✔
2130

2131
        return $that;
7✔
2132
    }
2133

2134
    /**
2135
     * Custom sort by value via "usort".
2136
     *
2137
     * EXAMPLE: <code>
2138
     * $callable = function ($a, $b) {
2139
     *     if ($a == $b) {
2140
     *         return 0;
2141
     *     }
2142
     *     return ($a > $b) ? 1 : -1;
2143
     * };
2144
     * $arrayy = a(['three' => 3, 'one' => 1, 'two' => 2]);
2145
     * $resultArrayy = $arrayy->customSortValues($callable); // Arrayy[1, 2, 3]
2146
     * </code>
2147
     *
2148
     * @see          http://php.net/manual/en/function.usort.php
2149
     *
2150
     * @param callable $callable
2151
     *
2152
     * @return $this
2153
     *               <p>(Mutable) Return this Arrayy object.</p>
2154
     *
2155
     * @phpstan-param  callable(T,T):int $callable
2156
     * @phpstan-return static
2157
     */
2158
    public function customSortValues(callable $callable): self
2159
    {
2160
        $this->generatorToArray();
77✔
2161

2162
        \usort($this->array, $callable);
77✔
2163

2164
        return $this;
77✔
2165
    }
2166

2167
    /**
2168
     * Custom sort by value via "usort".
2169
     *
2170
     * @see          http://php.net/manual/en/function.usort.php
2171
     *
2172
     * @param callable $callable
2173
     *
2174
     * @throws \InvalidArgumentException
2175
     *
2176
     * @return $this
2177
     *               <p>(Immutable) Return this Arrayy object.</p>
2178
     *
2179
     * @phpstan-param  callable(T,T):int $callable
2180
     * @phpstan-return static
2181
     * @psalm-mutation-free
2182
     */
2183
    public function customSortValuesImmutable($callable): self
2184
    {
2185
        $that = clone $this;
35✔
2186

2187
        /**
2188
         * @psalm-suppress ImpureMethodCall - object is already cloned
2189
         */
2190
        $that->customSortValues($callable);
35✔
2191

2192
        return $that;
35✔
2193
    }
2194

2195
    /**
2196
     * Delete the given key or keys.
2197
     *
2198
     * @param int|int[]|string|string[] $keyOrKeys
2199
     *
2200
     * @return void
2201
     */
2202
    public function delete($keyOrKeys)
2203
    {
2204
        $keyOrKeys = (array) $keyOrKeys;
63✔
2205

2206
        foreach ($keyOrKeys as $key) {
63✔
2207
            $this->offsetUnset($key);
63✔
2208
        }
2209
    }
2210

2211
    /**
2212
     * Return elements where the values that are only in the current array.
2213
     *
2214
     * EXAMPLE: <code>
2215
     * a([1 => 1, 2 => 2])->diff([1 => 1]); // Arrayy[2 => 2]
2216
     * </code>
2217
     *
2218
     * @param array ...$array
2219
     *
2220
     * @return static
2221
     *                <p>(Immutable)</p>
2222
     *
2223
     * @phpstan-param  array<TKey,T> ...$array
2224
     * @phpstan-return static
2225
     * @psalm-mutation-free
2226
     */
2227
    public function diff(array ...$array): self
2228
    {
2229
        if (\count($array) > 1) {
91✔
2230
            $array = \array_merge([], ...$array);
7✔
2231
        } else {
2232
            $array = $array[0];
91✔
2233
        }
2234

2235
        $generator = function () use ($array): \Generator {
91✔
2236
            foreach ($this->getGenerator() as $key => $value) {
91✔
2237
                if (\in_array($value, $array, true) === false) {
77✔
2238
                    yield $key => $value;
35✔
2239
                }
2240
            }
2241
        };
91✔
2242

2243
        return static::create(
91✔
2244
            $generator,
91✔
2245
            $this->iteratorClass,
91✔
2246
            false
91✔
2247
        );
91✔
2248
    }
2249

2250
    /**
2251
     * Return elements where the keys are only in the current array.
2252
     *
2253
     * @param array ...$array
2254
     *
2255
     * @return static
2256
     *                <p>(Immutable)</p>
2257
     *
2258
     * @phpstan-param  array<TKey,T> ...$array
2259
     * @phpstan-return static
2260
     * @psalm-mutation-free
2261
     */
2262
    public function diffKey(array ...$array): self
2263
    {
2264
        if (\count($array) > 1) {
63✔
2265
            $array = \array_replace([], ...$array);
7✔
2266
        } else {
2267
            $array = $array[0];
56✔
2268
        }
2269

2270
        $generator = function () use ($array): \Generator {
63✔
2271
            foreach ($this->getGenerator() as $key => $value) {
63✔
2272
                if (\array_key_exists($key, $array) === false) {
56✔
2273
                    yield $key => $value;
14✔
2274
                }
2275
            }
2276
        };
63✔
2277

2278
        return static::create(
63✔
2279
            $generator,
63✔
2280
            $this->iteratorClass,
63✔
2281
            false
63✔
2282
        );
63✔
2283
    }
2284

2285
    /**
2286
     * Return elements where the values and keys are only in the current array.
2287
     *
2288
     * @param array ...$array
2289
     *
2290
     * @return static
2291
     *                <p>(Immutable)</p>
2292
     *
2293
     * @phpstan-param  array<TKey,T> $array
2294
     * @phpstan-return static
2295
     * @psalm-mutation-free
2296
     */
2297
    public function diffKeyAndValue(array ...$array): self
2298
    {
2299
        if (\count($array) > 1) {
63✔
2300
            $array = \array_merge([], ...$array);
7✔
2301
        } else {
2302
            $array = $array[0];
56✔
2303
        }
2304

2305
        $generator = function () use ($array): \Generator {
63✔
2306
            foreach ($this->getGenerator() as $key => $value) {
63✔
2307
                $isset = isset($array[$key]);
56✔
2308

2309
                if (
2310
                    !$isset
56✔
2311
                    ||
2312
                    $array[$key] !== $value
56✔
2313
                ) {
2314
                    yield $key => $value;
28✔
2315
                }
2316
            }
2317
        };
63✔
2318

2319
        return static::create(
63✔
2320
            $generator,
63✔
2321
            $this->iteratorClass,
63✔
2322
            false
63✔
2323
        );
63✔
2324
    }
2325

2326
    /**
2327
     * Return elements where the values are only in the current multi-dimensional array.
2328
     *
2329
     * EXAMPLE: <code>
2330
     * a([1 => [1 => 1], 2 => [2 => 2]])->diffRecursive([1 => [1 => 1]]); // Arrayy[2 => [2 => 2]]
2331
     * </code>
2332
     *
2333
     * @param array                 $array
2334
     * @param array|\Generator|null $helperVariableForRecursion <p>(only for internal usage)</p>
2335
     *
2336
     * @return static
2337
     *                <p>(Immutable)</p>
2338
     *
2339
     * @phpstan-param  array<TKey,T> $array
2340
     * @phpstan-param  null|array<TKey,T>|\Generator<TKey,T> $helperVariableForRecursion
2341
     * @phpstan-return static
2342
     * @psalm-mutation-free
2343
     */
2344
    public function diffRecursive(array $array = [], $helperVariableForRecursion = null): self
2345
    {
2346
        // init
2347
        $result = [];
7✔
2348

2349
        if (
2350
            $helperVariableForRecursion !== null
7✔
2351
            &&
2352
            \is_array($helperVariableForRecursion)
7✔
2353
        ) {
2354
            $arrayForTheLoop = $helperVariableForRecursion;
×
2355
        } else {
2356
            $arrayForTheLoop = $this->getGenerator();
7✔
2357
        }
2358

2359
        foreach ($arrayForTheLoop as $key => $value) {
7✔
2360
            if ($value instanceof self) {
7✔
2361
                $value = $value->toArray();
7✔
2362
            }
2363

2364
            if (\array_key_exists($key, $array)) {
7✔
2365
                if ($value !== $array[$key]) {
7✔
2366
                    $result[$key] = $value;
7✔
2367
                }
2368
            } else {
2369
                $result[$key] = $value;
7✔
2370
            }
2371
        }
2372

2373
        return static::create(
7✔
2374
            $result,
7✔
2375
            $this->iteratorClass,
7✔
2376
            false
7✔
2377
        );
7✔
2378
    }
2379

2380
    /**
2381
     * Return elements where the values that are only in the new $array.
2382
     *
2383
     * EXAMPLE: <code>
2384
     * a([1 => 1])->diffReverse([1 => 1, 2 => 2]); // Arrayy[2 => 2]
2385
     * </code>
2386
     *
2387
     * @param array $array
2388
     *
2389
     * @return static
2390
     *                <p>(Immutable)</p>
2391
     *
2392
     * @phpstan-param  array<TKey,T> $array
2393
     * @phpstan-return static
2394
     * @psalm-mutation-free
2395
     */
2396
    public function diffReverse(array $array = []): self
2397
    {
2398
        return static::create(
56✔
2399
            \array_diff($array, $this->toArray()),
56✔
2400
            $this->iteratorClass,
56✔
2401
            false
56✔
2402
        );
56✔
2403
    }
2404

2405
    /**
2406
     * Divide an array into two arrays. One with keys and the other with values.
2407
     *
2408
     * EXAMPLE: <code>
2409
     * a(['a' => 1, 'b' => ''])->divide(); // Arrayy[Arrayy['a', 'b'], Arrayy[1, '']]
2410
     * </code>
2411
     *
2412
     * @return static
2413
     *                <p>(Immutable)</p>
2414
     *
2415
     * @phpstan-return static
2416
     * @psalm-mutation-free
2417
     */
2418
    public function divide(): self
2419
    {
2420
        return static::create(
7✔
2421
            [
7✔
2422
                $this->keys(),
7✔
2423
                $this->values(),
7✔
2424
            ],
7✔
2425
            $this->iteratorClass,
7✔
2426
            false
7✔
2427
        );
7✔
2428
    }
2429

2430
    /**
2431
     * Iterate over the current array and modify the array's value.
2432
     *
2433
     * EXAMPLE: <code>
2434
     * $result = A::create();
2435
     * $closure = function ($value) {
2436
     *     return ':' . $value . ':';
2437
     * };
2438
     * a(['foo', 'bar' => 'bis'])->each($closure); // Arrayy[':foo:', 'bar' => ':bis:']
2439
     * </code>
2440
     *
2441
     * @param \Closure $closure
2442
     *
2443
     * @return static
2444
     *                <p>(Immutable)</p>
2445
     *
2446
     * @phpstan-param \Closure(T,?TKey):T $closure
2447
     * @phpstan-return static
2448
     * @psalm-mutation-free
2449
     */
2450
    public function each(\Closure $closure): self
2451
    {
2452
        // init
2453
        $array = [];
42✔
2454

2455
        foreach ($this->getGenerator() as $key => $value) {
42✔
2456
            $array[$key] = $closure($value, $key);
42✔
2457
        }
2458

2459
        return static::create(
42✔
2460
            $array,
42✔
2461
            $this->iteratorClass,
42✔
2462
            false
42✔
2463
        );
42✔
2464
    }
2465

2466
    /**
2467
     * Sets the internal iterator to the last element in the array and returns this element.
2468
     *
2469
     * @return false|mixed
2470
     *
2471
     * @phpstan-return T|false
2472
     */
2473
    public function end()
2474
    {
2475
        if ($this->generator) {
×
2476
            $count = $this->count();
×
2477
            if ($count === 0) {
×
2478
                return false;
×
2479
            }
2480

2481
            $counter = 0;
×
2482
            /** @noinspection PhpUnusedLocalVariableInspection */
2483
            foreach ($this->getIterator() as $item) {
×
2484
                if (++$counter === $count - 1) {
×
2485
                    break;
×
2486
                }
2487
            }
2488
        }
2489

2490
        return \end($this->array);
×
2491
    }
2492

2493
    /**
2494
     * Check if a value is in the current array using a closure.
2495
     *
2496
     * EXAMPLE: <code>
2497
     * $callable = function ($value, $key) {
2498
     *     return 2 === $key and 'two' === $value;
2499
     * };
2500
     * a(['foo', 2 => 'two'])->exists($callable); // true
2501
     * </code>
2502
     *
2503
     * @param \Closure $closure
2504
     *
2505
     * @return bool
2506
     *              <p>Returns true if the given value is found, false otherwise.</p>
2507
     *
2508
     * @phpstan-param \Closure(T,TKey):bool $closure
2509
     */
2510
    public function exists(\Closure $closure): bool
2511
    {
2512
        // init
2513
        $isExists = false;
28✔
2514

2515
        foreach ($this->getGenerator() as $key => $value) {
28✔
2516
            if ($closure($value, $key)) {
21✔
2517
                $isExists = true;
7✔
2518

2519
                break;
7✔
2520
            }
2521
        }
2522

2523
        return $isExists;
28✔
2524
    }
2525

2526
    /**
2527
     * Fill the array until "$num" with "$default" values.
2528
     *
2529
     * EXAMPLE: <code>
2530
     * a(['bar'])->fillWithDefaults(3, 'foo'); // Arrayy['bar', 'foo', 'foo']
2531
     * </code>
2532
     *
2533
     * @param int   $num
2534
     * @param mixed $default
2535
     *
2536
     * @return static
2537
     *                <p>(Immutable)</p>
2538
     *
2539
     * @phpstan-param T $default
2540
     * @phpstan-return static
2541
     * @psalm-mutation-free
2542
     */
2543
    public function fillWithDefaults(int $num, $default = null): self
2544
    {
2545
        if ($num < 0) {
56✔
2546
            throw new \InvalidArgumentException('The $num parameter can only contain non-negative values.');
7✔
2547
        }
2548

2549
        $this->generatorToArray();
49✔
2550

2551
        $tmpArray = $this->array;
49✔
2552

2553
        $count = \count($tmpArray);
49✔
2554

2555
        while ($count < $num) {
49✔
2556
            $tmpArray[] = $default;
28✔
2557
            ++$count;
28✔
2558
        }
2559

2560
        return static::create(
49✔
2561
            $tmpArray,
49✔
2562
            $this->iteratorClass,
49✔
2563
            false
49✔
2564
        );
49✔
2565
    }
2566

2567
    /**
2568
     * Find all items in an array that pass the truth test.
2569
     *
2570
     * EXAMPLE: <code>
2571
     * $closure = function ($value) {
2572
     *     return $value % 2 !== 0;
2573
     * }
2574
     * a([1, 2, 3, 4])->filter($closure); // Arrayy[0 => 1, 2 => 3]
2575
     * </code>
2576
     *
2577
     * @param \Closure|null $closure [optional] <p>
2578
     *                               The callback function to use
2579
     *                               </p>
2580
     *                               <p>
2581
     *                               If no callback is supplied, all entries of
2582
     *                               input equal to false (see
2583
     *                               converting to
2584
     *                               boolean) will be removed.
2585
     *                               </p>
2586
     * @param int           $flag    [optional] <p>
2587
     *                               Flag determining what arguments are sent to <i>callback</i>:
2588
     *                               </p>
2589
     *                               <ul>
2590
     *                               <li>
2591
     *                               <b>ARRAY_FILTER_USE_KEY</b> (1) - pass key as the only argument
2592
     *                               to <i>callback</i> instead of the value
2593
     *                               </li>
2594
     *                               <li>
2595
     *                               <b>ARRAY_FILTER_USE_BOTH</b> (2) - pass both value and key as
2596
     *                               arguments to <i>callback</i> instead of the value
2597
     *                               </li>
2598
     *                               </ul>
2599
     *
2600
     * @return static
2601
     *                <p>(Immutable)</p>
2602
     *
2603
     * @phpstan-param null|(\Closure(T,TKey=):bool)|(\Closure(T):bool)|(\Closure(TKey):bool) $closure
2604
     * @phpstan-return static
2605
     * @psalm-mutation-free
2606
     */
2607
    public function filter($closure = null, int $flag = \ARRAY_FILTER_USE_BOTH)
2608
    {
2609
        if (!$closure) {
112✔
2610
            return $this->clean();
7✔
2611
        }
2612

2613
        if ($flag === \ARRAY_FILTER_USE_KEY) {
112✔
2614
            $generator = function () use ($closure) {
7✔
2615
                foreach ($this->getGenerator() as $key => $value) {
7✔
2616
                    if ($closure($key) === true) {
7✔
2617
                        yield $key => $value;
7✔
2618
                    }
2619
                }
2620
            };
7✔
2621
        } elseif ($flag === \ARRAY_FILTER_USE_BOTH) {
112✔
2622
            /** @noinspection PhpSillyAssignmentInspection - hack for phpstan - https://github.com/phpstan/phpstan/issues/4192 */
2623
            /** @phpstan-var \Closure(T,TKey):bool $closure */
2624
            $closure = $closure;
112✔
2625

2626
            $generator = function () use ($closure) {
112✔
2627
                foreach ($this->getGenerator() as $key => $value) {
105✔
2628
                    if ($closure($value, $key) === true) {
98✔
2629
                        yield $key => $value;
77✔
2630
                    }
2631
                }
2632
            };
112✔
2633
        } else {
2634
            $generator = function () use ($closure) {
7✔
2635
                foreach ($this->getGenerator() as $key => $value) {
7✔
2636
                    if ($closure($value) === true) {
7✔
2637
                        yield $key => $value;
7✔
2638
                    }
2639
                }
2640
            };
7✔
2641
        }
2642

2643
        return static::create(
112✔
2644
            $generator,
112✔
2645
            $this->iteratorClass,
112✔
2646
            false
112✔
2647
        );
112✔
2648
    }
2649

2650
    /**
2651
     * Filters an array of objects (or a numeric array of associative arrays) based on the value of a particular
2652
     * property within that.
2653
     *
2654
     * @param string      $property
2655
     * @param mixed       $value
2656
     * @param string|null $comparisonOp
2657
     *                                  <p>
2658
     *                                  'eq' (equals),<br />
2659
     *                                  'gt' (greater),<br />
2660
     *                                  'gte' || 'ge' (greater or equals),<br />
2661
     *                                  'lt' (less),<br />
2662
     *                                  'lte' || 'le' (less or equals),<br />
2663
     *                                  'ne' (not equals),<br />
2664
     *                                  'contains',<br />
2665
     *                                  'notContains',<br />
2666
     *                                  'newer' (via strtotime),<br />
2667
     *                                  'older' (via strtotime),<br />
2668
     *                                  </p>
2669
     *
2670
     * @return static
2671
     *                <p>(Immutable)</p>
2672
     *
2673
     * @phpstan-param array<array-key, mixed>|T $value
2674
     * @phpstan-return static
2675
     * @psalm-mutation-free
2676
     *
2677
     * @psalm-suppress MissingClosureReturnType
2678
     * @psalm-suppress MissingClosureParamType
2679
     */
2680
    public function filterBy(
2681
        string $property,
2682
        $value,
2683
        ?string $comparisonOp = null
2684
    ): self {
2685
        if (!$comparisonOp) {
7✔
2686
            $comparisonOp = \is_array($value) ? 'contains' : 'eq';
7✔
2687
        }
2688

2689
        $ops = [
7✔
2690
            'eq' => static function ($item, $prop, $value): bool {
7✔
2691
                return $item[$prop] === $value;
7✔
2692
            },
7✔
2693
            'gt' => static function ($item, $prop, $value): bool {
7✔
2694
                return $item[$prop] > $value;
×
2695
            },
7✔
2696
            'ge' => static function ($item, $prop, $value): bool {
7✔
2697
                return $item[$prop] >= $value;
×
2698
            },
7✔
2699
            'gte' => static function ($item, $prop, $value): bool {
7✔
2700
                return $item[$prop] >= $value;
×
2701
            },
7✔
2702
            'lt' => static function ($item, $prop, $value): bool {
7✔
2703
                return $item[$prop] < $value;
7✔
2704
            },
7✔
2705
            'le' => static function ($item, $prop, $value): bool {
7✔
2706
                return $item[$prop] <= $value;
×
2707
            },
7✔
2708
            'lte' => static function ($item, $prop, $value): bool {
7✔
2709
                return $item[$prop] <= $value;
×
2710
            },
7✔
2711
            'ne' => static function ($item, $prop, $value): bool {
7✔
2712
                return $item[$prop] !== $value;
×
2713
            },
7✔
2714
            'contains' => static function ($item, $prop, $value): bool {
7✔
2715
                return \in_array($item[$prop], (array) $value, true);
7✔
2716
            },
7✔
2717
            'notContains' => static function ($item, $prop, $value): bool {
7✔
2718
                return !\in_array($item[$prop], (array) $value, true);
×
2719
            },
7✔
2720
            'newer' => static function ($item, $prop, $value): bool {
7✔
2721
                return \strtotime($item[$prop]) > \strtotime($value);
×
2722
            },
7✔
2723
            'older' => static function ($item, $prop, $value): bool {
7✔
2724
                return \strtotime($item[$prop]) < \strtotime($value);
×
2725
            },
7✔
2726
        ];
7✔
2727

2728
        $result = \array_values(
7✔
2729
            \array_filter(
7✔
2730
                $this->toArray(false, true),
7✔
2731
                static function ($item) use (
7✔
2732
                    $property,
7✔
2733
                    $value,
7✔
2734
                    $ops,
7✔
2735
                    $comparisonOp
7✔
2736
                ) {
7✔
2737
                    $item = (array) $item;
7✔
2738
                    $itemArrayy = static::create($item);
7✔
2739
                    $item[$property] = $itemArrayy->get($property, []);
7✔
2740

2741
                    return $ops[$comparisonOp]($item, $property, $value);
7✔
2742
                }
7✔
2743
            )
7✔
2744
        );
7✔
2745

2746
        return static::create(
7✔
2747
            $result,
7✔
2748
            $this->iteratorClass,
7✔
2749
            false
7✔
2750
        );
7✔
2751
    }
2752

2753
    /**
2754
     * Find the first item in an array that passes the truth test, otherwise return false.
2755
     *
2756
     * EXAMPLE: <code>
2757
     * $search = 'foo';
2758
     * $closure = function ($value, $key) use ($search) {
2759
     *     return $value === $search;
2760
     * };
2761
     * a(['foo', 'bar', 'lall'])->find($closure); // 'foo'
2762
     * </code>
2763
     *
2764
     * @param \Closure $closure
2765
     *
2766
     * @return false|mixed
2767
     *                     <p>Return false if we did not find the value.</p>
2768
     *
2769
     * @phpstan-param \Closure(T,TKey):bool $closure
2770
     * @phpstan-return T|false
2771
     */
2772
    public function find(\Closure $closure)
2773
    {
2774
        foreach ($this->getGenerator() as $key => $value) {
63✔
2775
            if ($closure($value, $key)) {
49✔
2776
                return $value;
42✔
2777
            }
2778
        }
2779

2780
        return false;
21✔
2781
    }
2782

2783
    /**
2784
     * Find the key of the first item in an array that passes the truth test, otherwise return false.
2785
     *
2786
     * EXAMPLE: <code>
2787
     * $search = 'foo';
2788
     * $closure = function ($value, $key) use ($search) {
2789
     *     return $value === $search;
2790
     * };
2791
     * a(['foo', 'bar', 'lall'])->findKey($closure); // 0
2792
     * </code>
2793
     *
2794
     * @param \Closure $closure
2795
     *
2796
     * @return false|int|string
2797
     *                          <p>Return false if we did not find the key.</p>
2798
     *
2799
     * @phpstan-param \Closure(T,TKey):bool $closure
2800
     * @phpstan-return TKey|false
2801
     */
2802
    public function findKey(\Closure $closure)
2803
    {
2804
        foreach ($this->getGenerator() as $key => $value) {
91✔
2805
            if ($closure($value, $key)) {
84✔
2806
                return $key;
70✔
2807
            }
2808
        }
2809

2810
        return false;
28✔
2811
    }
2812

2813
    /**
2814
     * find by ...
2815
     *
2816
     * EXAMPLE: <code>
2817
     * $array = [
2818
     *     0 => ['id' => 123, 'name' => 'foo', 'group' => 'primary', 'value' => 123456, 'when' => '2014-01-01'],
2819
     *     1 => ['id' => 456, 'name' => 'bar', 'group' => 'primary', 'value' => 1468, 'when' => '2014-07-15'],
2820
     * ];
2821
     * a($array)->filterBy('name', 'foo'); // Arrayy[0 => ['id' => 123, 'name' => 'foo', 'group' => 'primary', 'value' => 123456, 'when' => '2014-01-01']]
2822
     * </code>
2823
     *
2824
     * @param string $property
2825
     * @param mixed  $value
2826
     * @param string $comparisonOp
2827
     *
2828
     * @return static
2829
     *                <p>(Immutable)</p>
2830
     *
2831
     * @phpstan-param array<array-key, mixed>|T $value
2832
     * @phpstan-return static
2833
     * @psalm-mutation-free
2834
     */
2835
    public function findBy(string $property, $value, string $comparisonOp = 'eq'): self
2836
    {
2837
        return $this->filterBy($property, $value, $comparisonOp);
7✔
2838
    }
2839

2840
    /**
2841
     * Get the first value from the current array.
2842
     *
2843
     * EXAMPLE: <code>
2844
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->first(); // 'foo'
2845
     * </code>
2846
     *
2847
     * @return mixed|null
2848
     *                    <p>Return null if there wasn't a element.</p>
2849
     *
2850
     * @phpstan-return T|null
2851
     * @psalm-mutation-free
2852
     */
2853
    public function first()
2854
    {
2855
        $key_first = $this->firstKey();
164✔
2856
        if ($key_first === null) {
164✔
2857
            return null;
21✔
2858
        }
2859

2860
        return $this->get($key_first);
143✔
2861
    }
2862

2863
    /**
2864
     * Get the first key from the current array.
2865
     *
2866
     * @return mixed|null
2867
     *                    <p>Return null if there wasn't a element.</p>
2868
     *
2869
     * @phpstan-return TKey|null
2870
     *
2871
     * @psalm-mutation-free
2872
     */
2873
    public function firstKey()
2874
    {
2875
        $this->generatorToArray();
213✔
2876

2877
        /** @phpstan-var TKey|null $return - help for phpstan */
2878
        $return = \array_key_first($this->array);
213✔
2879

2880
        return $return;
213✔
2881
    }
2882

2883
    /**
2884
     * Get the first value(s) from the current array.
2885
     * And will return an empty array if there was no first entry.
2886
     *
2887
     * EXAMPLE: <code>
2888
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->firstsImmutable(2); // Arrayy[0 => 'foo', 1 => 'bar']
2889
     * </code>
2890
     *
2891
     * @param int|null $number <p>How many values you will take?</p>
2892
     *
2893
     * @return static
2894
     *                <p>(Immutable)</p>
2895
     *
2896
     * @phpstan-return static
2897
     * @psalm-mutation-free
2898
     */
2899
    public function firstsImmutable(?int $number = null): self
2900
    {
2901
        $arrayTmp = $this->toArray();
259✔
2902

2903
        if ($number === null) {
259✔
2904
            $array = (array) \array_shift($arrayTmp);
98✔
2905
        } else {
2906
            $array = \array_splice($arrayTmp, 0, $number);
161✔
2907
        }
2908

2909
        return static::create(
259✔
2910
            $array,
259✔
2911
            $this->iteratorClass,
259✔
2912
            false
259✔
2913
        );
259✔
2914
    }
2915

2916
    /**
2917
     * Get the first value(s) from the current array.
2918
     * And will return an empty array if there was no first entry.
2919
     *
2920
     * @param int|null $number <p>How many values you will take?</p>
2921
     *
2922
     * @return static
2923
     *                <p>(Immutable)</p>
2924
     *
2925
     * @phpstan-return static
2926
     * @psalm-mutation-free
2927
     */
2928
    public function firstsKeys(?int $number = null): self
2929
    {
2930
        $arrayTmp = $this->keys()->toArray();
21✔
2931

2932
        if ($number === null) {
21✔
2933
            $array = (array) \array_shift($arrayTmp);
×
2934
        } else {
2935
            $array = \array_splice($arrayTmp, 0, $number);
21✔
2936
        }
2937

2938
        return static::create(
21✔
2939
            $array,
21✔
2940
            $this->iteratorClass,
21✔
2941
            false
21✔
2942
        );
21✔
2943
    }
2944

2945
    /**
2946
     * Get and remove the first value(s) from the current array.
2947
     * And will return an empty array if there was no first entry.
2948
     *
2949
     * EXAMPLE: <code>
2950
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->firstsMutable(); // 'foo'
2951
     * </code>
2952
     *
2953
     * @param int|null $number <p>How many values you will take?</p>
2954
     *
2955
     * @return $this
2956
     *               <p>(Mutable)</p>
2957
     *
2958
     * @phpstan-return ($number is null ? static : static)
2959
     */
2960
    public function firstsMutable(?int $number = null): self
2961
    {
2962
        $this->generatorToArray();
238✔
2963

2964
        if ($number === null) {
238✔
2965
            $shift = \array_shift($this->array);
133✔
2966
            $this->array = $shift !== null ? [$shift] : [];
133✔
2967
        } else {
2968
            $splice = \array_splice($this->array, 0, $number);
105✔
2969
            $this->array = $splice;
105✔
2970
        }
2971

2972
        return $this;
238✔
2973
    }
2974

2975
    /**
2976
     * Exchanges all keys with their associated values in an array.
2977
     *
2978
     * EXAMPLE: <code>
2979
     * a([0 => 'foo', 1 => 'bar'])->flip(); // Arrayy['foo' => 0, 'bar' => 1]
2980
     * </code>
2981
     *
2982
     * @return static
2983
     *                <p>(Immutable)</p>
2984
     *
2985
     * @phpstan-return static
2986
     * @psalm-mutation-free
2987
     */
2988
    public function flip(): self
2989
    {
2990
        $generator = function (): \Generator {
7✔
2991
            foreach ($this->getGenerator() as $key => $value) {
7✔
2992
                yield (string) $value => $key;
7✔
2993
            }
2994
        };
7✔
2995

2996
        return static::create(
7✔
2997
            $generator,
7✔
2998
            $this->iteratorClass,
7✔
2999
            false
7✔
3000
        );
7✔
3001
    }
3002

3003
    /**
3004
     * Get a value from an array (optional using dot-notation).
3005
     *
3006
     * EXAMPLE: <code>
3007
     * $arrayy = a(['user' => ['lastname' => 'Moelleken']]);
3008
     * $arrayy->get('user.lastname'); // 'Moelleken'
3009
     * // ---
3010
     * $arrayy = new A();
3011
     * $arrayy['user'] = ['lastname' => 'Moelleken'];
3012
     * $arrayy['user.firstname'] = 'Lars';
3013
     * $arrayy['user']['lastname']; // Moelleken
3014
     * $arrayy['user.lastname']; // Moelleken
3015
     * $arrayy['user.firstname']; // Lars
3016
     * </code>
3017
     *
3018
     * @param int|string $key
3019
     *                                   <p>The key to look for.</p>
3020
     * @param mixed      $fallback
3021
     *                                   <p>Value to fallback to.</p>
3022
     * @param array|null $array
3023
     *                                   <p>The array to get from, if it's set to "null" we use the current array from the
3024
     *                                   class.</p>
3025
     * @param bool       $useByReference
3026
     *
3027
     * @return mixed|static
3028
     *
3029
     * @phpstan-param array-key|null $key
3030
     * @phpstan-param array<array-key,mixed>|array<TKey,T> $array
3031
     * @psalm-mutation-free
3032
     */
3033
    public function get(
3034
        $key = null,
3035
        $fallback = null,
3036
        ?array $array = null,
3037
        bool $useByReference = false
3038
    ) {
3039
        if ($array === null && $key === null) {
1,993✔
3040
            if ($useByReference) {
7✔
3041
                return $this;
×
3042
            }
3043

3044
            return clone $this;
7✔
3045
        }
3046

3047
        if ($array !== null) {
1,993✔
3048
            if ($useByReference) {
28✔
3049
                $usedArray = &$array;
×
3050
            } else {
3051
                $usedArray = $array;
28✔
3052
            }
3053
        } else {
3054
            $this->generatorToArray();
1,972✔
3055

3056
            if ($useByReference) {
1,972✔
3057
                $usedArray = &$this->array;
1,174✔
3058
            } else {
3059
                $usedArray = $this->array;
927✔
3060
            }
3061
        }
3062

3063
        if ($key === null) {
1,993✔
3064
            return static::create(
7✔
3065
                [],
7✔
3066
                $this->iteratorClass,
7✔
3067
                false
7✔
3068
            )->createByReference($usedArray);
7✔
3069
        }
3070

3071
        // php cast "bool"-index into "int"-index
3072
        /* @phpstan-ignore identical.alwaysFalse */
3073
        if ((bool) $key === $key) {
1,993✔
3074
            $key = (int) $key;
×
3075
        }
3076

3077
        if (\array_key_exists($key, $usedArray) === true) {
1,993✔
3078
            if (\is_array($usedArray[$key])) {
1,727✔
3079
                return static::create(
143✔
3080
                    [],
143✔
3081
                    $this->iteratorClass,
143✔
3082
                    false
143✔
3083
                )->createByReference($usedArray[$key]);
143✔
3084
            }
3085

3086
            return $usedArray[$key];
1,615✔
3087
        }
3088

3089
        // crawl through array, get key according to object or not
3090
        $usePath = false;
427✔
3091
        if (
3092
            $this->pathSeparator
427✔
3093
            &&
3094
            (string) $key === $key
427✔
3095
            &&
3096
            \strpos($key, $this->pathSeparator) !== false
427✔
3097
        ) {
3098
            $segments = \explode($this->pathSeparator, (string) $key);
217✔
3099
            $usePath = true;
217✔
3100
            $usedArrayTmp = $usedArray; // do not use the reference for dot-annotations
217✔
3101

3102
            foreach ($segments as $segment) {
217✔
3103
                if (
3104
                    (
3105
                        \is_array($usedArrayTmp)
217✔
3106
                        ||
217✔
3107
                        $usedArrayTmp instanceof \ArrayAccess
217✔
3108
                    )
3109
                    &&
3110
                    isset($usedArrayTmp[$segment])
217✔
3111
                ) {
3112
                    $usedArrayTmp = $usedArrayTmp[$segment];
210✔
3113

3114
                    continue;
210✔
3115
                }
3116

3117
                if (
3118
                    \is_object($usedArrayTmp) === true
98✔
3119
                    &&
3120
                    \property_exists($usedArrayTmp, $segment)
98✔
3121
                ) {
3122
                    $usedArrayTmp = $usedArrayTmp->{$segment};
7✔
3123

3124
                    continue;
7✔
3125
                }
3126

3127
                if ($segments[0] === '*') {
91✔
3128
                    $segmentsTmp = $segments;
7✔
3129
                    unset($segmentsTmp[0]);
7✔
3130
                    $keyTmp = \implode('.', $segmentsTmp);
7✔
3131
                    $returnTmp = static::create(
7✔
3132
                        [],
7✔
3133
                        $this->iteratorClass,
7✔
3134
                        false
7✔
3135
                    );
7✔
3136
                    foreach ($this->getAll() as $dataTmp) {
7✔
3137
                        if ($dataTmp instanceof self) {
7✔
3138
                            $returnTmp->add($dataTmp->get($keyTmp));
×
3139

3140
                            continue;
×
3141
                        }
3142

3143
                        if (
3144
                            (
3145
                                \is_array($dataTmp)
7✔
3146
                                ||
7✔
3147
                                $dataTmp instanceof \ArrayAccess
7✔
3148
                            )
3149
                            &&
3150
                            isset($dataTmp[$keyTmp])
7✔
3151
                        ) {
3152
                            $returnTmp->add($dataTmp[$keyTmp]);
×
3153

3154
                            continue;
×
3155
                        }
3156

3157
                        if (
3158
                            \is_object($dataTmp) === true
7✔
3159
                            &&
3160
                            \property_exists($dataTmp, $keyTmp)
7✔
3161
                        ) {
3162
                            $returnTmp->add($dataTmp->{$keyTmp});
7✔
3163

3164
                            continue;
7✔
3165
                        }
3166
                    }
3167

3168
                    if ($returnTmp->count() > 0) {
7✔
3169
                        return $returnTmp;
7✔
3170
                    }
3171
                }
3172

3173
                return $fallback instanceof \Closure ? $fallback() : $fallback;
84✔
3174
            }
3175

3176
            if (\is_array($usedArrayTmp)) {
196✔
3177
                return static::create(
42✔
3178
                    [],
42✔
3179
                    $this->iteratorClass,
42✔
3180
                    false
42✔
3181
                )->createByReference($usedArrayTmp);
42✔
3182
            }
3183

3184
            return $usedArrayTmp;
196✔
3185
        }
3186

3187
        if (!isset($usedArray[$key])) {
210✔
3188
            return $fallback instanceof \Closure ? $fallback() : $fallback;
210✔
3189
        }
3190

3191
        return static::create(
×
3192
            [],
×
3193
            $this->iteratorClass,
×
3194
            false
×
3195
        )->createByReference($usedArray);
×
3196
    }
3197

3198
    /**
3199
     * alias: for "Arrayy->toArray()"
3200
     *
3201
     * @return array
3202
     *
3203
     * @see          Arrayy::getArray()
3204
     *
3205
     * @phpstan-return array<TKey,T>
3206
     */
3207
    public function getAll(): array
3208
    {
3209
        /** @var array<TKey,T> $return */
3210
        $return = $this->toArray();
105✔
3211

3212
        return $return;
105✔
3213
    }
3214

3215
    /**
3216
     * Get the current array from the "Arrayy"-object.
3217
     *
3218
     * alias for "toArray()"
3219
     *
3220
     * @param bool $convertAllArrayyElements <p>
3221
     *                                       Convert all Child-"Arrayy" objects also to arrays.
3222
     *                                       </p>
3223
     * @param bool $preserveKeys             <p>
3224
     *                                       e.g.: A generator maybe return the same key more than once,
3225
     *                                       so maybe you will ignore the keys.
3226
     *                                       </p>
3227
     *
3228
     * @return array
3229
     *
3230
     * @phpstan-return array<array-key,T>|array<TKey,T>
3231
     * @psalm-mutation-free
3232
     *
3233
     * @see Arrayy::toArray()
3234
     */
3235
    public function getArray(
3236
        bool $convertAllArrayyElements = false,
3237
        bool $preserveKeys = true
3238
    ): array {
3239
        return $this->toArray(
3,675✔
3240
            $convertAllArrayyElements,
3,675✔
3241
            $preserveKeys
3,675✔
3242
        );
3,675✔
3243
    }
3244

3245
    /**
3246
     * Create an instance from JSON using the built-in mapper.
3247
     *
3248
     * For Arrayy models with property checks enabled, phpdoc array-shape annotations,
3249
     * legacy `@property` definitions, and native declared properties are used for metadata and type checks.
3250
     * Add a property-level `@var` annotation if a native `array` property also needs
3251
     * element-type validation.
3252
     *
3253
     * @param string $json
3254
     *
3255
     * @return static
3256
     *                <p>(Immutable)</p>
3257
     */
3258
    public static function createFromJsonMapper(string $json)
3259
    {
3260
        // init
3261
        $class = static::create();
56✔
3262

3263
        $jsonObject = \json_decode($json, false);
56✔
3264

3265
        $mapper = new \Arrayy\Mapper\Json();
56✔
3266
        $mapper->undefinedPropertyHandler = static function ($object, $key, $jsonValue) use ($class) {
56✔
3267
            if ($class->checkPropertiesMismatchInConstructor) {
×
3268
                throw new \TypeError('Property mismatch - input: ' . \print_r(['key' => $key, 'jsonValue' => $jsonValue], true) . ' for object: ' . \get_class($object));
×
3269
            }
3270
        };
56✔
3271

3272
        /** @var static $return - hack for phpstan */
3273
        $return = $mapper->map($jsonObject, $class);
56✔
3274

3275
        return $return;
35✔
3276
    }
3277

3278
    /**
3279
     * @return array<array-key,TypeCheckInterface>|TypeCheckArray<array-key,TypeCheckInterface>
3280
     *
3281
     * @internal
3282
     */
3283
    public function getPhpDocPropertiesFromClass()
3284
    {
3285
        if ($this->properties === []) {
166✔
3286
            $this->properties = $this->getPropertiesFromPhpDoc();
96✔
3287
        }
3288

3289
        return $this->properties;
166✔
3290
    }
3291

3292
    /**
3293
     * Get the current array from the "Arrayy"-object as list.
3294
     *
3295
     * alias for "toList()"
3296
     *
3297
     * @param bool $convertAllArrayyElements <p>
3298
     *                                       Convert all Child-"Arrayy" objects also to arrays.
3299
     *                                       </p>
3300
     *
3301
     * @return array
3302
     *
3303
     * @phpstan-return list<T>
3304
     * @psalm-mutation-free
3305
     *
3306
     * @see Arrayy::toList()
3307
     */
3308
    public function getList(bool $convertAllArrayyElements = false): array
3309
    {
3310
        return $this->toList($convertAllArrayyElements);
7✔
3311
    }
3312

3313
    /**
3314
     * Returns the values from a single column of the input array, identified by
3315
     * the $columnKey, can be used to extract data-columns from multi-arrays.
3316
     *
3317
     * EXAMPLE: <code>
3318
     * a([['foo' => 'bar', 'id' => 1], ['foo => 'lall', 'id' => 2]])->getColumn('foo', 'id'); // Arrayy[1 => 'bar', 2 => 'lall']
3319
     * </code>
3320
     *
3321
     * INFO: Optionally, you may provide an $indexKey to index the values in the returned
3322
     *       array by the values from the $indexKey column in the input array.
3323
     *
3324
     * @param int|string|null $columnKey
3325
     * @param int|string|null $indexKey
3326
     *
3327
     * @return static
3328
     *                <p>(Immutable)</p>
3329
     *
3330
     * @phpstan-return static
3331
     * @psalm-mutation-free
3332
     */
3333
    public function getColumn($columnKey = null, $indexKey = null): self
3334
    {
3335
        if ($columnKey === null && $indexKey === null) {
7✔
3336
            $generator = function () {
7✔
3337
                foreach ($this->getGenerator() as $value) {
7✔
3338
                    yield $value;
7✔
3339
                }
3340
            };
7✔
3341
        } else {
3342
            $generator = function () use ($columnKey, $indexKey) {
7✔
3343
                foreach ($this->getGenerator() as $value) {
7✔
3344
                    // reset
3345
                    $newKey = null;
7✔
3346
                    $newValue = null;
7✔
3347
                    $newValueFound = false;
7✔
3348

3349
                    if ($indexKey !== null) {
7✔
3350
                        foreach ($value as $keyInner => $valueInner) {
7✔
3351
                            if ($indexKey === $keyInner) {
7✔
3352
                                $newKey = $valueInner;
7✔
3353
                            }
3354

3355
                            if ($columnKey === $keyInner) {
7✔
3356
                                $newValue = $valueInner;
7✔
3357
                                $newValueFound = true;
7✔
3358
                            }
3359
                        }
3360
                    } else {
3361
                        foreach ($value as $keyInner => $valueInner) {
7✔
3362
                            if ($columnKey === $keyInner) {
7✔
3363
                                $newValue = $valueInner;
7✔
3364
                                $newValueFound = true;
7✔
3365
                            }
3366
                        }
3367
                    }
3368

3369
                    if ($newValueFound === false) {
7✔
3370
                        if ($newKey !== null) {
7✔
3371
                            yield $newKey => $value;
7✔
3372
                        } else {
3373
                            yield $value;
7✔
3374
                        }
3375
                    } else {
3376
                        /** @noinspection NestedPositiveIfStatementsInspection */
3377
                        if ($newKey !== null) {
7✔
3378
                            yield $newKey => $newValue;
7✔
3379
                        } else {
3380
                            yield $newValue;
7✔
3381
                        }
3382
                    }
3383
                }
3384
            };
7✔
3385
        }
3386

3387
        return static::create(
7✔
3388
            $generator,
7✔
3389
            $this->iteratorClass,
7✔
3390
            false
7✔
3391
        );
7✔
3392
    }
3393

3394
    /**
3395
     * Get the current array from the "Arrayy"-object as generator by reference.
3396
     *
3397
     * @return \Generator
3398
     *
3399
     * @phpstan-return \Generator<mixed,T>|\Generator<TKey,T>
3400
     */
3401
    public function &getGeneratorByReference(): \Generator
3402
    {
3403
        if ($this->generator instanceof ArrayyRewindableGenerator) {
602✔
3404
            foreach ($this->generator as $key => $value) {
119✔
3405
                yield $key => $value;
119✔
3406
            }
3407

3408
            return;
35✔
3409
        }
3410

3411
        // -> false-positive -> see "&$value"
3412
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
3413
        foreach ($this->array as $key => &$value) {
490✔
3414
            yield $key => $value;
441✔
3415
        }
3416
    }
3417

3418
    /**
3419
     * Get the current array from the "Arrayy"-object as generator.
3420
     *
3421
     * @return \Generator
3422
     *
3423
     * @phpstan-return \Generator<mixed,T>|\Generator<TKey,T>
3424
     * @psalm-mutation-free
3425
     */
3426
    public function getGenerator(): \Generator
3427
    {
3428
        if ($this->generator instanceof ArrayyRewindableGenerator) {
7,915✔
3429
            yield from $this->generator;
574✔
3430

3431
            return;
560✔
3432
        }
3433

3434
        yield from $this->array;
7,901✔
3435
    }
3436

3437
    /**
3438
     * Get the current array from the "Arrayy"-object as generator.
3439
     *
3440
     * @return \Generator
3441
     *
3442
     * @phpstan-return \Generator<mixed,T>|\Generator<TKey,T>
3443
     * @psalm-mutation-free
3444
     */
3445
    public function getBackwardsGenerator(): \Generator
3446
    {
3447
        yield from $this->reverseKeepIndex();
28✔
3448
    }
3449

3450
    /**
3451
     * alias: for "Arrayy->keys()"
3452
     *
3453
     * @return static
3454
     *                <p>(Immutable)</p>
3455
     *
3456
     * @see          Arrayy::keys()
3457
     *
3458
     * @phpstan-return static
3459
     * @psalm-mutation-free
3460
     */
3461
    public function getKeys()
3462
    {
3463
        return $this->keys();
14✔
3464
    }
3465

3466
    /**
3467
     * Get the current array from the "Arrayy"-object as object.
3468
     *
3469
     * @return \stdClass
3470
     */
3471
    public function getObject(): \stdClass
3472
    {
3473
        return self::arrayToObject($this->toArray());
28✔
3474
    }
3475

3476
    /**
3477
     * alias: for "Arrayy->randomImmutable()"
3478
     *
3479
     * @return static
3480
     *                <p>(Immutable)</p>
3481
     *
3482
     * @see          Arrayy::randomImmutable()
3483
     *
3484
     * @phpstan-return static
3485
     */
3486
    public function getRandom(): self
3487
    {
3488
        return $this->randomImmutable();
28✔
3489
    }
3490

3491
    /**
3492
     * alias: for "Arrayy->randomKey()"
3493
     *
3494
     * @return mixed|null
3495
     *                    <p>Get a key/index or null if there wasn't a key/index.</p>
3496
     *
3497
     * @phpstan-return null|TKey
3498
     *
3499
     * @see Arrayy::randomKey()
3500
     */
3501
    public function getRandomKey()
3502
    {
3503
        return $this->randomKey();
21✔
3504
    }
3505

3506
    /**
3507
     * alias: for "Arrayy->randomKeys()"
3508
     *
3509
     * @param int $number
3510
     *
3511
     * @return static
3512
     *                <p>(Immutable)</p>
3513
     *
3514
     * @see          Arrayy::randomKeys()
3515
     *
3516
     * @phpstan-return static
3517
     */
3518
    public function getRandomKeys(int $number): self
3519
    {
3520
        return $this->randomKeys($number);
56✔
3521
    }
3522

3523
    /**
3524
     * alias: for "Arrayy->randomValue()"
3525
     *
3526
     * @return mixed|null
3527
     *                    <p>Get a random value or null if there wasn't a value.</p>
3528
     *
3529
     * @phpstan-return null|T
3530
     *
3531
     * @see Arrayy::randomValue()
3532
     */
3533
    public function getRandomValue()
3534
    {
3535
        return $this->randomValue();
21✔
3536
    }
3537

3538
    /**
3539
     * alias: for "Arrayy->randomValues()"
3540
     *
3541
     * @param int $number
3542
     *
3543
     * @return static
3544
     *                <p>(Immutable)</p>
3545
     *
3546
     * @see          Arrayy::randomValues()
3547
     *
3548
     * @phpstan-return static
3549
     */
3550
    public function getRandomValues(int $number): self
3551
    {
3552
        return $this->randomValues($number);
42✔
3553
    }
3554

3555
    /**
3556
     * Gets all values.
3557
     *
3558
     * @return static
3559
     *                <p>The values of all elements in this array, in the order they
3560
     *                appear in the array.</p>
3561
     *
3562
     * @phpstan-return static
3563
     */
3564
    public function getValues()
3565
    {
3566
        $this->generatorToArray(false);
28✔
3567

3568
        return static::create(
28✔
3569
            \array_values($this->array),
28✔
3570
            $this->iteratorClass,
28✔
3571
            false
28✔
3572
        );
28✔
3573
    }
3574

3575
    /**
3576
     * Gets all values via Generator.
3577
     *
3578
     * @return \Generator
3579
     *                    <p>The values of all elements in this array, in the order they
3580
     *                    appear in the array as Generator.</p>
3581
     *
3582
     * @phpstan-return \Generator<TKey,T>
3583
     */
3584
    public function getValuesYield(): \Generator
3585
    {
3586
        yield from $this->getGenerator();
28✔
3587
    }
3588

3589
    /**
3590
     * Group values from a array according to the results of a closure.
3591
     *
3592
     * @param callable|int|string $grouper  <p>A callable function name.</p>
3593
     * @param bool                $saveKeys
3594
     *
3595
     * @return static
3596
     *                <p>(Immutable)</p>
3597
     *
3598
     * @phpstan-param \Closure(T,TKey):TKey|TKey $grouper
3599
     * @phpstan-return static
3600
     * @psalm-mutation-free
3601
     */
3602
    public function group($grouper, bool $saveKeys = false): self
3603
    {
3604
        // init
3605
        $result = [];
28✔
3606

3607
        // Iterate over values, group by property/results from closure.
3608
        foreach ($this->getGenerator() as $key => $value) {
28✔
3609
            if (\is_callable($grouper) === true) {
28✔
3610
                $groupKey = $grouper($value, $key);
21✔
3611
            } else {
3612
                $groupKey = $this->get($grouper);
7✔
3613
            }
3614

3615
            $newValue = $this->get($groupKey, null, $result);
28✔
3616

3617
            if ($groupKey instanceof self) {
28✔
3618
                $groupKey = $groupKey->toArray();
×
3619
            }
3620

3621
            if ($newValue instanceof self) {
28✔
3622
                $newValue = $newValue->toArray();
28✔
3623
            }
3624

3625
            // Add to result.
3626
            if ($groupKey !== null) {
28✔
3627
                $result[$groupKey] = $newValue;
21✔
3628

3629
                if ($saveKeys) {
21✔
3630
                    $result[$groupKey][$key] = $value;
14✔
3631
                } else {
3632
                    $result[$groupKey][] = $value;
7✔
3633
                }
3634
            }
3635
        }
3636

3637
        return static::create(
28✔
3638
            $result,
28✔
3639
            $this->iteratorClass,
28✔
3640
            false
28✔
3641
        );
28✔
3642
    }
3643

3644
    /**
3645
     * Check if an array has a given key.
3646
     *
3647
     * @param mixed $key
3648
     *
3649
     * @return bool
3650
     *
3651
     * @phpstan-param null|array-key|array<array-key> $key
3652
     */
3653
    public function has($key): bool
3654
    {
3655
        static $UN_FOUND = null;
210✔
3656

3657
        if ($UN_FOUND === null) {
210✔
3658
            // Generate unique string to use as marker.
3659
            $UN_FOUND = 'arrayy--' . \uniqid('arrayy', true);
7✔
3660
        }
3661

3662
        if (\is_array($key)) {
210✔
3663
            if ($key === []) {
7✔
3664
                return false;
×
3665
            }
3666

3667
            foreach ($key as $keyTmp) {
7✔
3668
                $found = ($this->get($keyTmp, $UN_FOUND) !== $UN_FOUND);
7✔
3669
                if ($found === false) {
7✔
3670
                    return false;
7✔
3671
                }
3672
            }
3673

3674
            return true;
7✔
3675
        }
3676

3677
        return $this->get($key, $UN_FOUND) !== $UN_FOUND;
203✔
3678
    }
3679

3680
    /**
3681
     * Check if an array has a given value.
3682
     *
3683
     * INFO: If you need to search recursive please use ```contains($value, true)```.
3684
     *
3685
     * @param mixed $value
3686
     *
3687
     * @return bool
3688
     *
3689
     * @phpstan-param T $value
3690
     */
3691
    public function hasValue($value): bool
3692
    {
3693
        return $this->contains($value);
7✔
3694
    }
3695

3696
    /**
3697
     * Implodes the values of this array.
3698
     *
3699
     * EXAMPLE: <code>
3700
     * a([0 => -9, 1, 2])->implode('|'); // '-9|1|2'
3701
     * </code>
3702
     *
3703
     * @param string $glue
3704
     * @param string $prefix
3705
     *
3706
     * @return string
3707
     * @psalm-mutation-free
3708
     */
3709
    public function implode(string $glue = '', string $prefix = ''): string
3710
    {
3711
        return $prefix . $this->implode_recursive($glue, $this->toArray(), false);
203✔
3712
    }
3713

3714
    /**
3715
     * Implodes the keys of this array.
3716
     *
3717
     * @param string $glue
3718
     *
3719
     * @return string
3720
     * @psalm-mutation-free
3721
     */
3722
    public function implodeKeys(string $glue = ''): string
3723
    {
3724
        return $this->implode_recursive($glue, $this->toArray(), true);
56✔
3725
    }
3726

3727
    /**
3728
     * Given a list and an iterate-function that returns
3729
     * a key for each element in the list (or a property name),
3730
     * returns an object with an index of each item.
3731
     *
3732
     * @param int|string $key
3733
     *
3734
     * @return static
3735
     *                <p>(Immutable)</p>
3736
     *
3737
     * @phpstan-param array-key $key
3738
     * @phpstan-return static
3739
     * @psalm-mutation-free
3740
     */
3741
    public function indexBy($key): self
3742
    {
3743
        // init
3744
        $results = [];
28✔
3745

3746
        foreach ($this->getGenerator() as $a) {
28✔
3747
            if (\array_key_exists($key, $a) === true) {
28✔
3748
                $results[$a[$key]] = $a;
21✔
3749
            }
3750
        }
3751

3752
        return static::create(
28✔
3753
            $results,
28✔
3754
            $this->iteratorClass,
28✔
3755
            false
28✔
3756
        );
28✔
3757
    }
3758

3759
    /**
3760
     * alias: for "Arrayy->searchIndex()"
3761
     *
3762
     * @param mixed $value
3763
     *                     <p>The value to search for.</p>
3764
     *
3765
     * @return false|int|string
3766
     *
3767
     * @phpstan-param T $value
3768
     * @phpstan-return false|TKey
3769
     *
3770
     * @see Arrayy::searchIndex()
3771
     */
3772
    public function indexOf($value)
3773
    {
3774
        return $this->searchIndex($value);
28✔
3775
    }
3776

3777
    /**
3778
     * Get everything but the last..$to items.
3779
     *
3780
     * EXAMPLE: <code>
3781
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->initial(2); // Arrayy[0 => 'foo']
3782
     * </code>
3783
     *
3784
     * @param int $to
3785
     *
3786
     * @return static
3787
     *                <p>(Immutable)</p>
3788
     *
3789
     * @phpstan-return static
3790
     * @psalm-mutation-free
3791
     */
3792
    public function initial(int $to = 1): self
3793
    {
3794
        return $this->firstsImmutable(\count($this->toArray(), \COUNT_NORMAL) - $to);
84✔
3795
    }
3796

3797
    /**
3798
     * Return an array with all elements found in input array.
3799
     *
3800
     * EXAMPLE: <code>
3801
     * a(['foo', 'bar'])->intersection(['bar', 'baz']); // Arrayy['bar']
3802
     * </code>
3803
     *
3804
     * @param array $search
3805
     * @param bool  $keepKeys
3806
     *
3807
     * @return static
3808
     *                <p>(Immutable)</p>
3809
     *
3810
     * @phpstan-param  array<TKey,T> $search
3811
     * @phpstan-return static
3812
     * @psalm-mutation-free
3813
     */
3814
    public function intersection(array $search, bool $keepKeys = false): self
3815
    {
3816
        if ($keepKeys) {
28✔
3817
            /**
3818
             * @psalm-suppress MissingClosureReturnType
3819
             * @psalm-suppress MissingClosureParamType
3820
             */
3821
            return static::create(
7✔
3822
                \array_uintersect(
7✔
3823
                    $this->toArray(),
7✔
3824
                    $search,
7✔
3825
                    static function ($a, $b) {
7✔
3826
                        return $a === $b ? 0 : -1;
7✔
3827
                    }
7✔
3828
                ),
7✔
3829
                $this->iteratorClass,
7✔
3830
                false
7✔
3831
            );
7✔
3832
        }
3833

3834
        return static::create(
21✔
3835
            \array_values(\array_intersect($this->toArray(), $search)),
21✔
3836
            $this->iteratorClass,
21✔
3837
            false
21✔
3838
        );
21✔
3839
    }
3840

3841
    /**
3842
     * Return an array with all elements found in input array.
3843
     *
3844
     * @param array ...$array
3845
     *
3846
     * @return static
3847
     *                <p>(Immutable)</p>
3848
     *
3849
     * @phpstan-param  array<array<TKey,T>> ...$array
3850
     * @phpstan-return static
3851
     * @psalm-mutation-free
3852
     */
3853
    public function intersectionMulti(...$array): self
3854
    {
3855
        return static::create(
7✔
3856
            \array_values(\array_intersect($this->toArray(), ...$array)),
7✔
3857
            $this->iteratorClass,
7✔
3858
            false
7✔
3859
        );
7✔
3860
    }
3861

3862
    /**
3863
     * Return a boolean flag which indicates whether the two input arrays have any common elements.
3864
     *
3865
     * EXAMPLE: <code>
3866
     * a(['foo', 'bar'])->intersects(['fΓΆΓΆ', 'bΓ€r']); // false
3867
     * </code>
3868
     *
3869
     * @param array $search
3870
     *
3871
     * @return bool
3872
     *
3873
     * @phpstan-param array<TKey,T> $search
3874
     */
3875
    public function intersects(array $search): bool
3876
    {
3877
        return $this->intersection($search)->count() > 0;
7✔
3878
    }
3879

3880
    /**
3881
     * Invoke a function on all of an array's values.
3882
     *
3883
     * @param callable $callable
3884
     * @param mixed    $arguments
3885
     *
3886
     * @return static
3887
     *                <p>(Immutable)</p>
3888
     *
3889
     * @phpstan-param  callable(T,mixed=):mixed $callable
3890
     * @phpstan-return static|static
3891
     * @psalm-mutation-free
3892
     */
3893
    public function invoke($callable, $arguments = []): self
3894
    {
3895
        // If one argument given for each iteration, create an array for it.
3896
        if (!\is_array($arguments)) {
7✔
3897
            $arguments = \array_fill(
7✔
3898
                0,
7✔
3899
                $this->count(),
7✔
3900
                $arguments
7✔
3901
            );
7✔
3902
        }
3903

3904
        // If the callable has arguments, pass them.
3905
        if ($arguments) {
7✔
3906
            $array = \array_map($callable, $this->toArray(), $arguments);
7✔
3907
        } else {
3908
            $array = $this->map($callable);
7✔
3909
        }
3910

3911
        return static::create(
7✔
3912
            $array,
7✔
3913
            $this->iteratorClass,
7✔
3914
            false
7✔
3915
        );
7✔
3916
    }
3917

3918
    /**
3919
     * Check whether array is associative or not.
3920
     *
3921
     * EXAMPLE: <code>
3922
     * a(['foo' => 'bar', 2, 3])->isAssoc(); // true
3923
     * </code>
3924
     *
3925
     * @param bool $recursive
3926
     *
3927
     * @return bool
3928
     *              <p>Returns true if associative, false otherwise.</p>
3929
     */
3930
    public function isAssoc(bool $recursive = false): bool
3931
    {
3932
        if ($this->isEmpty()) {
105✔
3933
            return false;
21✔
3934
        }
3935

3936
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
3937
        foreach ($this->keys($recursive)->getGeneratorByReference() as &$key) {
91✔
3938
            if ((string) $key !== $key) {
91✔
3939
                return false;
77✔
3940
            }
3941
        }
3942

3943
        return true;
21✔
3944
    }
3945

3946
    /**
3947
     * Check if a given key or keys are empty.
3948
     *
3949
     * @param int|int[]|string|string[]|null $keys
3950
     *
3951
     * @return bool
3952
     *              <p>Returns true if empty, false otherwise.</p>
3953
     * @psalm-mutation-free
3954
     */
3955
    public function isEmpty($keys = null): bool
3956
    {
3957
        if ($this->generator) {
315✔
3958
            return $this->toArray() === [];
×
3959
        }
3960

3961
        if ($keys === null) {
315✔
3962
            return $this->array === [];
301✔
3963
        }
3964

3965
        foreach ((array) $keys as $key) {
14✔
3966
            if (!empty($this->get($key))) {
14✔
3967
                return false;
14✔
3968
            }
3969
        }
3970

3971
        return true;
14✔
3972
    }
3973

3974
    /**
3975
     * Check if the current array is equal to the given "$array" or not.
3976
     *
3977
     * EXAMPLE: <code>
3978
     * a(['πŸ’©'])->isEqual(['πŸ’©']); // true
3979
     * </code>
3980
     *
3981
     * @param array $array
3982
     *
3983
     * @return bool
3984
     *
3985
     * @phpstan-param array<TKey,T> $array
3986
     */
3987
    public function isEqual(array $array): bool
3988
    {
3989
        return $this->toArray() === $array;
7✔
3990
    }
3991

3992
    /**
3993
     * Check if the current array is a multi-array.
3994
     *
3995
     * EXAMPLE: <code>
3996
     * a(['foo' => [1, 2 , 3]])->isMultiArray(); // true
3997
     * </code>
3998
     *
3999
     * @return bool
4000
     */
4001
    public function isMultiArray(): bool
4002
    {
4003
        foreach ($this->getGenerator() as $value) {
154✔
4004
            if (\is_array($value)) {
140✔
4005
                return true;
35✔
4006
            }
4007
        }
4008

4009
        return false;
126✔
4010
    }
4011

4012
    /**
4013
     * Check whether array is numeric or not.
4014
     *
4015
     * @return bool
4016
     *              <p>Returns true if numeric, false otherwise.</p>
4017
     */
4018
    public function isNumeric(): bool
4019
    {
4020
        if ($this->isEmpty()) {
35✔
4021
            return false;
14✔
4022
        }
4023

4024
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4025
        foreach ($this->keys()->getGeneratorByReference() as &$key) {
28✔
4026
            if ((int) $key !== $key) {
28✔
4027
                return false;
14✔
4028
            }
4029
        }
4030

4031
        return true;
14✔
4032
    }
4033

4034
    /**
4035
     * Check if the current array is sequential [0, 1, 2, 3, 4, 5 ...] or not.
4036
     *
4037
     * EXAMPLE: <code>
4038
     * a([0 => 'foo', 1 => 'lall', 2 => 'foobar'])->isSequential(); // true
4039
     * </code>
4040
     *
4041
     * INFO: If the array is empty we count it as non-sequential.
4042
     *
4043
     * @param bool $recursive
4044
     *
4045
     * @return bool
4046
     * @psalm-mutation-free
4047
     */
4048
    public function isSequential(bool $recursive = false): bool
4049
    {
4050
        $i = 0;
70✔
4051
        foreach ($this->getGenerator() as $key => $value) {
70✔
4052
            if (
4053
                $recursive
63✔
4054
                &&
4055
                (\is_array($value) || $value instanceof \Traversable)
63✔
4056
                &&
4057
                self::create($value)->isSequential() === false
63✔
4058
            ) {
4059
                return false;
7✔
4060
            }
4061

4062
            if ($key !== $i) {
63✔
4063
                return false;
21✔
4064
            }
4065

4066
            ++$i;
56✔
4067
        }
4068

4069
        return !($i === 0);
63✔
4070
    }
4071

4072
    /**
4073
     * @return array
4074
     *
4075
     * @phpstan-return array<TKey,T>
4076
     */
4077
    public function jsonSerialize(): array
4078
    {
4079
        /** @var array<TKey,T> $return */
4080
        $return = $this->toArray();
14✔
4081

4082
        return $return;
14✔
4083
    }
4084

4085
    /**
4086
     * Gets the key/index of the element at the current internal iterator position.
4087
     *
4088
     * @return int|string|null
4089
     * @phpstan-return array-key|null
4090
     */
4091
    public function key()
4092
    {
4093
        if ($this->generator) {
×
4094
            return $this->generator->key();
×
4095
        }
4096

4097
        return \key($this->array);
×
4098
    }
4099

4100
    /**
4101
     * Checks if the given key exists in the provided array.
4102
     *
4103
     * INFO: This method only use "array_key_exists()" if you want to use "dot"-notation,
4104
     *       then you need to use "Arrayy->offsetExists()".
4105
     *
4106
     * @param int|string $key the key to look for
4107
     *
4108
     * @return bool
4109
     * @psalm-mutation-free
4110
     */
4111
    public function keyExists($key): bool
4112
    {
4113
        foreach ($this->getGenerator() as $keyTmp => $value) {
1,454✔
4114
            if ($key === $keyTmp) {
1,419✔
4115
                return true;
1,314✔
4116
            }
4117
        }
4118

4119
        return false;
1,022✔
4120
    }
4121

4122
    /**
4123
     * Get all keys from the current array.
4124
     *
4125
     * EXAMPLE: <code>
4126
     * a([1 => 'foo', 2 => 'foo2', 3 => 'bar'])->keys(); // Arrayy[1, 2, 3]
4127
     * </code>
4128
     *
4129
     * @param bool       $recursive
4130
     *                                  [optional] <p>
4131
     *                                  Get all keys, also from all sub-arrays from an multi-dimensional array.
4132
     *                                  </p>
4133
     * @param mixed|null $search_values
4134
     *                                  [optional] <p>
4135
     *                                  If specified, then only keys containing these values are returned.
4136
     *                                  </p>
4137
     * @param bool       $strict
4138
     *                                  [optional] <p>
4139
     *                                  Determines if strict comparison (===) should be used during the search.
4140
     *                                  </p>
4141
     *
4142
     * @return static
4143
     *                <p>(Immutable) An array of all the keys in input.</p>
4144
     *
4145
     * @phpstan-param null|T|T[] $search_values
4146
     * @phpstan-return static
4147
     *
4148
     * @psalm-mutation-free
4149
     */
4150
    public function keys(
4151
        bool $recursive = false,
4152
        $search_values = null,
4153
        bool $strict = true
4154
    ): self {
4155
        // recursive
4156

4157
        if ($recursive === true) {
210✔
4158
            $array = $this->array_keys_recursive(
28✔
4159
                null,
28✔
4160
                $search_values,
28✔
4161
                $strict
28✔
4162
            );
28✔
4163

4164
            return static::create(
28✔
4165
                $array,
28✔
4166
                $this->iteratorClass,
28✔
4167
                false
28✔
4168
            );
28✔
4169
        }
4170

4171
        // non recursive
4172

4173
        if ($search_values === null) {
203✔
4174
            $arrayFunction = function (): \Generator {
203✔
4175
                foreach ($this->getGenerator() as $key => $value) {
203✔
4176
                    yield $key;
189✔
4177
                }
4178
            };
203✔
4179
        } else {
4180
            $arrayFunction = function () use ($search_values, $strict): \Generator {
7✔
4181
                $is_array_tmp = \is_array($search_values);
7✔
4182

4183
                /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4184
                foreach ($this->getGeneratorByReference() as $key => &$value) {
7✔
4185
                    if (
4186
                        (
4187
                            $is_array_tmp === false
7✔
4188
                            &&
7✔
4189
                            $strict === true
7✔
4190
                            &&
7✔
4191
                            $search_values === $value
7✔
4192
                        )
4193
                        ||
4194
                        (
4195
                            $is_array_tmp === false
7✔
4196
                            &&
7✔
4197
                            $strict === false
7✔
4198
                            &&
7✔
4199
                            $search_values == $value
7✔
4200
                        )
4201
                        ||
4202
                        (
4203
                            $is_array_tmp === true
7✔
4204
                            &&
7✔
4205
                            \in_array($value, $search_values, $strict)
7✔
4206
                        )
4207
                    ) {
4208
                        yield $key;
7✔
4209
                    }
4210
                }
4211
            };
7✔
4212
        }
4213

4214
        return static::create(
203✔
4215
            $arrayFunction,
203✔
4216
            $this->iteratorClass,
203✔
4217
            false
203✔
4218
        );
203✔
4219
    }
4220

4221
    /**
4222
     * Sort an array by key in reverse order.
4223
     *
4224
     * @param int $sort_flags [optional] <p>
4225
     *                        You may modify the behavior of the sort using the optional
4226
     *                        parameter sort_flags, for details
4227
     *                        see sort.
4228
     *                        </p>
4229
     *
4230
     * @return $this
4231
     *               <p>(Mutable) Return this Arrayy object.</p>
4232
     *
4233
     * @phpstan-return static
4234
     */
4235
    public function krsort(int $sort_flags = 0): self
4236
    {
4237
        $this->generatorToArray();
28✔
4238

4239
        \krsort($this->array, $sort_flags);
28✔
4240

4241
        return $this;
28✔
4242
    }
4243

4244
    /**
4245
     * Sort an array by key in reverse order.
4246
     *
4247
     * @param int $sort_flags [optional] <p>
4248
     *                        You may modify the behavior of the sort using the optional
4249
     *                        parameter sort_flags, for details
4250
     *                        see sort.
4251
     *                        </p>
4252
     *
4253
     * @return $this
4254
     *               <p>(Immutable)</p>
4255
     *
4256
     * @phpstan-return static
4257
     * @psalm-mutation-free
4258
     */
4259
    public function krsortImmutable(int $sort_flags = 0): self
4260
    {
4261
        $that = clone $this;
28✔
4262

4263
        /**
4264
         * @psalm-suppress ImpureMethodCall - object is already cloned
4265
         */
4266
        $that->krsort($sort_flags);
28✔
4267

4268
        return $that;
28✔
4269
    }
4270

4271
    /**
4272
     * Get the last value from the current array.
4273
     *
4274
     * EXAMPLE: <code>
4275
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->last(); // 'lall'
4276
     * </code>
4277
     *
4278
     * @return mixed|null
4279
     *                    <p>Return null if there wasn't a element.</p>
4280
     *
4281
     * @phpstan-return T|null
4282
     * @psalm-mutation-free
4283
     */
4284
    public function last()
4285
    {
4286
        $key_last = $this->lastKey();
119✔
4287
        if ($key_last === null) {
119✔
4288
            return null;
14✔
4289
        }
4290

4291
        /** @var T $value_last */
4292
        $value_last = $this->get($key_last);
105✔
4293

4294
        return $value_last;
105✔
4295
    }
4296

4297
    /**
4298
     * Get the last key from the current array.
4299
     *
4300
     * @return mixed|null
4301
     *                    <p>Return null if there wasn't a element.</p>
4302
     *
4303
     * @phpstan-return null|TKey
4304
     * @psalm-mutation-free
4305
     */
4306
    public function lastKey()
4307
    {
4308
        $this->generatorToArray();
147✔
4309

4310
        /** @phpstan-var TKey|null $return - help for phpstan */
4311
        $return = \array_key_last($this->array);
147✔
4312

4313
        return $return;
147✔
4314
    }
4315

4316
    /**
4317
     * Get the last value(s) from the current array.
4318
     *
4319
     * EXAMPLE: <code>
4320
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->lasts(2); // Arrayy[0 => 'bar', 1 => 'lall']
4321
     * </code>
4322
     *
4323
     * @param int|null $number
4324
     *
4325
     * @return static
4326
     *                <p>(Immutable)</p>
4327
     *
4328
     * @phpstan-return static
4329
     * @psalm-mutation-free
4330
     */
4331
    public function lastsImmutable(?int $number = null): self
4332
    {
4333
        if ($this->isEmpty()) {
91✔
4334
            return static::create(
7✔
4335
                [],
7✔
4336
                $this->iteratorClass,
7✔
4337
                false
7✔
4338
            );
7✔
4339
        }
4340

4341
        if ($number === null) {
84✔
4342
            $poppedValue = $this->last();
56✔
4343

4344
            if ($poppedValue === null) {
56✔
4345
                $poppedValue = [$poppedValue];
7✔
4346
            } else {
4347
                $poppedValue = (array) $poppedValue;
49✔
4348
            }
4349

4350
            $arrayy = static::create(
56✔
4351
                $poppedValue,
56✔
4352
                $this->iteratorClass,
56✔
4353
                false
56✔
4354
            );
56✔
4355
        } else {
4356
            $arrayy = $this->rest(-$number);
28✔
4357
        }
4358

4359
        return $arrayy;
84✔
4360
    }
4361

4362
    /**
4363
     * Get the last value(s) from the current array.
4364
     *
4365
     * EXAMPLE: <code>
4366
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->lasts(2); // Arrayy[0 => 'bar', 1 => 'lall']
4367
     * </code>
4368
     *
4369
     * @param int|null $number
4370
     *
4371
     * @return $this
4372
     *               <p>(Mutable)</p>
4373
     *
4374
     * @phpstan-return static
4375
     */
4376
    public function lastsMutable(?int $number = null): self
4377
    {
4378
        if ($this->isEmpty()) {
91✔
4379
            return $this;
7✔
4380
        }
4381

4382
        $this->array = $this->lastsImmutable($number)->toArray();
84✔
4383
        $this->generator = null;
84✔
4384

4385
        return $this;
84✔
4386
    }
4387

4388
    /**
4389
     * Count the values from the current array.
4390
     *
4391
     * alias: for "Arrayy->count()"
4392
     *
4393
     * @param int $mode
4394
     *
4395
     * @return int
4396
     *
4397
     * @see Arrayy::count()
4398
     */
4399
    public function length(int $mode = \COUNT_NORMAL): int
4400
    {
4401
        return $this->count($mode);
140✔
4402
    }
4403

4404
    /**
4405
     * Apply the given function to the every element of the array,
4406
     * collecting the results.
4407
     *
4408
     * EXAMPLE: <code>
4409
     * a(['foo', 'Foo'])->map('mb_strtoupper'); // Arrayy['FOO', 'FOO']
4410
     * </code>
4411
     *
4412
     * @param callable $callable
4413
     * @param bool     $useKeyAsSecondParameter
4414
     * @param mixed    ...$arguments
4415
     *
4416
     * @return static
4417
     *                <p>(Immutable) Arrayy object with modified elements.</p>
4418
     *
4419
     * @template T2
4420
     *              <p>The output value type.</p>
4421
     *
4422
     * @phpstan-param callable(T,TKey=,mixed=):T2 $callable
4423
     * @phpstan-return static
4424
     * @psalm-mutation-free
4425
     */
4426
    public function map(
4427
        callable $callable,
4428
        bool $useKeyAsSecondParameter = false,
4429
        ...$arguments
4430
    ) {
4431
        /**
4432
         * @psalm-suppress ImpureFunctionCall - func_num_args is only used to detect the number of args
4433
         */
4434
        $useArguments = \func_num_args() > 2;
49✔
4435

4436
        return static::create(
49✔
4437
            function () use ($useArguments, $callable, $useKeyAsSecondParameter, $arguments) {
49✔
4438
                foreach ($this->getGenerator() as $key => $value) {
49✔
4439
                    if ($useArguments) {
42✔
4440
                        if ($useKeyAsSecondParameter) {
21✔
4441
                            yield $key => $callable($value, $key, ...$arguments);
×
4442
                        } else {
4443
                            yield $key => $callable($value, ...$arguments);
21✔
4444
                        }
4445
                    } else {
4446
                        /** @noinspection NestedPositiveIfStatementsInspection */
4447
                        if ($useKeyAsSecondParameter) {
42✔
4448
                            yield $key => $callable($value, $key);
×
4449
                        } else {
4450
                            yield $key => $callable($value);
42✔
4451
                        }
4452
                    }
4453
                }
4454
            },
49✔
4455
            $this->iteratorClass,
49✔
4456
            false
49✔
4457
        );
49✔
4458
    }
4459

4460
    /**
4461
     * Check if all items in current array match a truth test.
4462
     *
4463
     * EXAMPLE: <code>
4464
     * $closure = function ($value, $key) {
4465
     *     return ($value % 2 === 0);
4466
     * };
4467
     * a([2, 4, 8])->matches($closure); // true
4468
     * </code>
4469
     *
4470
     * @param \Closure $closure
4471
     *
4472
     * @return bool
4473
     *
4474
     * @phpstan-param \Closure(T,TKey):bool $closure
4475
     */
4476
    public function matches(\Closure $closure): bool
4477
    {
4478
        if ($this->count() === 0) {
105✔
4479
            return false;
14✔
4480
        }
4481

4482
        foreach ($this->getGenerator() as $key => $value) {
91✔
4483
            $value = $closure($value, $key);
91✔
4484

4485
            if ($value === false) {
91✔
4486
                return false;
49✔
4487
            }
4488
        }
4489

4490
        return true;
49✔
4491
    }
4492

4493
    /**
4494
     * Check if any item in the current array matches a truth test.
4495
     *
4496
     * EXAMPLE: <code>
4497
     * $closure = function ($value, $key) {
4498
     *     return ($value % 2 === 0);
4499
     * };
4500
     * a([1, 4, 7])->matches($closure); // true
4501
     * </code>
4502
     *
4503
     * @param \Closure $closure
4504
     *
4505
     * @return bool
4506
     *
4507
     * @phpstan-param \Closure(T,TKey):bool $closure
4508
     */
4509
    public function matchesAny(\Closure $closure): bool
4510
    {
4511
        if ($this->count() === 0) {
98✔
4512
            return false;
14✔
4513
        }
4514

4515
        foreach ($this->getGenerator() as $key => $value) {
84✔
4516
            $value = $closure($value, $key);
84✔
4517

4518
            if ($value === true) {
84✔
4519
                return true;
63✔
4520
            }
4521
        }
4522

4523
        return false;
28✔
4524
    }
4525

4526
    /**
4527
     * Get the max value from an array.
4528
     *
4529
     * EXAMPLE: <code>
4530
     * a([-9, -8, -7, 1.32])->max(); // 1.32
4531
     * </code>
4532
     *
4533
     * @return false|float|int|string
4534
     *                                <p>Will return false if there are no values.</p>
4535
     */
4536
    public function max()
4537
    {
4538
        if ($this->count() === 0) {
77✔
4539
            return false;
7✔
4540
        }
4541

4542
        $max = false;
70✔
4543
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4544
        foreach ($this->getGeneratorByReference() as &$value) {
70✔
4545
            if (
4546
                $max === false
70✔
4547
                ||
4548
                $value > $max
70✔
4549
            ) {
4550
                $max = $value;
70✔
4551
            }
4552
        }
4553

4554
        return $max;
70✔
4555
    }
4556

4557
    /**
4558
     * Merge the new $array into the current array.
4559
     *
4560
     * - keep key,value from the current array, also if the index is in the new $array
4561
     *
4562
     * EXAMPLE: <code>
4563
     * $array1 = [1 => 'one', 'foo' => 'bar1'];
4564
     * $array2 = ['foo' => 'bar2', 3 => 'three'];
4565
     * a($array1)->mergeAppendKeepIndex($array2); // Arrayy[1 => 'one', 'foo' => 'bar2', 3 => 'three']
4566
     * // ---
4567
     * $array1 = [0 => 'one', 1 => 'foo'];
4568
     * $array2 = [0 => 'foo', 1 => 'bar2'];
4569
     * a($array1)->mergeAppendKeepIndex($array2); // Arrayy[0 => 'foo', 1 => 'bar2']
4570
     * </code>
4571
     *
4572
     * @param array $array
4573
     * @param bool  $recursive
4574
     *
4575
     * @return static
4576
     *                <p>(Immutable)</p>
4577
     *
4578
     * @phpstan-param  array<int|TKey,T> $array
4579
     * @phpstan-return static
4580
     * @psalm-mutation-free
4581
     */
4582
    public function mergeAppendKeepIndex(array $array = [], bool $recursive = false): self
4583
    {
4584
        if ($recursive === true) {
231✔
4585
            $array = $this->getArrayRecursiveHelperArrayy($array);
63✔
4586
            $result = \array_replace_recursive($this->toArray(), $array);
63✔
4587
        } else {
4588
            $result = \array_replace($this->toArray(), $array);
168✔
4589
        }
4590

4591
        return static::create(
231✔
4592
            $result,
231✔
4593
            $this->iteratorClass,
231✔
4594
            false
231✔
4595
        );
231✔
4596
    }
4597

4598
    /**
4599
     * Merge the new $array into the current array.
4600
     *
4601
     * - replace duplicate assoc-keys from the current array with the key,values from the new $array
4602
     * - create new indexes
4603
     *
4604
     * EXAMPLE: <code>
4605
     * $array1 = [1 => 'one', 'foo' => 'bar1'];
4606
     * $array2 = ['foo' => 'bar2', 3 => 'three'];
4607
     * a($array1)->mergeAppendNewIndex($array2); // Arrayy[0 => 'one', 'foo' => 'bar2', 1 => 'three']
4608
     * // ---
4609
     * $array1 = [0 => 'one', 1 => 'foo'];
4610
     * $array2 = [0 => 'foo', 1 => 'bar2'];
4611
     * a($array1)->mergeAppendNewIndex($array2); // Arrayy[0 => 'one', 1 => 'foo', 2 => 'foo', 3 => 'bar2']
4612
     * </code>
4613
     *
4614
     * @param array $array
4615
     * @param bool  $recursive
4616
     *
4617
     * @return static
4618
     *                <p>(Immutable)</p>
4619
     *
4620
     * @phpstan-param  array<TKey,T> $array
4621
     * @phpstan-return static
4622
     * @psalm-mutation-free
4623
     */
4624
    public function mergeAppendNewIndex(array $array = [], bool $recursive = false): self
4625
    {
4626
        if ($recursive === true) {
140✔
4627
            $array = $this->getArrayRecursiveHelperArrayy($array);
35✔
4628
            $result = \array_merge_recursive($this->toArray(), $array);
35✔
4629
        } else {
4630
            $result = \array_merge($this->toArray(), $array);
105✔
4631
        }
4632

4633
        return static::create(
140✔
4634
            $result,
140✔
4635
            $this->iteratorClass,
140✔
4636
            false
140✔
4637
        );
140✔
4638
    }
4639

4640
    /**
4641
     * Merge the the current array into the $array.
4642
     *
4643
     * - use key,value from the new $array, also if the index is in the current array
4644
     *
4645
     * EXAMPLE: <code>
4646
     * $array1 = [1 => 'one', 'foo' => 'bar1'];
4647
     * $array2 = ['foo' => 'bar2', 3 => 'three'];
4648
     * a($array1)->mergePrependKeepIndex($array2); // Arrayy['foo' => 'bar1', 3 => 'three', 1 => 'one']
4649
     * // ---
4650
     * $array1 = [0 => 'one', 1 => 'foo'];
4651
     * $array2 = [0 => 'foo', 1 => 'bar2'];
4652
     * a($array1)->mergePrependKeepIndex($array2); // Arrayy[0 => 'one', 1 => 'foo']
4653
     * </code>
4654
     *
4655
     * @param array $array
4656
     * @param bool  $recursive
4657
     *
4658
     * @return static
4659
     *                <p>(Immutable)</p>
4660
     *
4661
     * @phpstan-param  array<TKey,T> $array
4662
     * @phpstan-return static
4663
     * @psalm-mutation-free
4664
     */
4665
    public function mergePrependKeepIndex(array $array = [], bool $recursive = false): self
4666
    {
4667
        if ($recursive === true) {
119✔
4668
            $array = $this->getArrayRecursiveHelperArrayy($array);
28✔
4669
            $result = \array_replace_recursive($array, $this->toArray());
28✔
4670
        } else {
4671
            $result = \array_replace($array, $this->toArray());
91✔
4672
        }
4673

4674
        return static::create(
119✔
4675
            $result,
119✔
4676
            $this->iteratorClass,
119✔
4677
            false
119✔
4678
        );
119✔
4679
    }
4680

4681
    /**
4682
     * Merge the current array into the new $array.
4683
     *
4684
     * - replace duplicate assoc-keys from new $array with the key,values from the current array
4685
     * - create new indexes
4686
     *
4687
     * EXAMPLE: <code>
4688
     * $array1 = [1 => 'one', 'foo' => 'bar1'];
4689
     * $array2 = ['foo' => 'bar2', 3 => 'three'];
4690
     * a($array1)->mergePrependNewIndex($array2); // Arrayy['foo' => 'bar1', 0 => 'three', 1 => 'one']
4691
     * // ---
4692
     * $array1 = [0 => 'one', 1 => 'foo'];
4693
     * $array2 = [0 => 'foo', 1 => 'bar2'];
4694
     * a($array1)->mergePrependNewIndex($array2); // Arrayy[0 => 'foo', 1 => 'bar2', 2 => 'one', 3 => 'foo']
4695
     * </code>
4696
     *
4697
     * @param array $array
4698
     * @param bool  $recursive
4699
     *
4700
     * @return static
4701
     *                <p>(Immutable)</p>
4702
     *
4703
     * @phpstan-param  array<TKey,T> $array
4704
     * @phpstan-return static
4705
     * @psalm-mutation-free
4706
     */
4707
    public function mergePrependNewIndex(array $array = [], bool $recursive = false): self
4708
    {
4709
        if ($recursive === true) {
147✔
4710
            $array = $this->getArrayRecursiveHelperArrayy($array);
49✔
4711
            $result = \array_merge_recursive($array, $this->toArray());
49✔
4712
        } else {
4713
            $result = \array_merge($array, $this->toArray());
98✔
4714
        }
4715

4716
        return static::create(
147✔
4717
            $result,
147✔
4718
            $this->iteratorClass,
147✔
4719
            false
147✔
4720
        );
147✔
4721
    }
4722

4723
    /**
4724
     * Return a meta object with property names from phpdoc array-shape annotations,
4725
     * `@property` tags, and native declared properties.
4726
     *
4727
     * @return ArrayyMeta|mixed|static
4728
     */
4729
    public static function meta()
4730
    {
4731
        return (new ArrayyMeta())->getMetaObject(static::class);
320✔
4732
    }
4733

4734
    /**
4735
     * Get the min value from an array.
4736
     *
4737
     * EXAMPLE: <code>
4738
     * a([-9, -8, -7, 1.32])->min(); // -9
4739
     * </code>
4740
     *
4741
     * @return false|mixed
4742
     *                     <p>Will return false if there are no values.</p>
4743
     *
4744
     * @phpstan-return false|T
4745
     */
4746
    public function min()
4747
    {
4748
        if ($this->count() === 0) {
77✔
4749
            return false;
7✔
4750
        }
4751

4752
        $min = false;
70✔
4753
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4754
        foreach ($this->getGeneratorByReference() as &$value) {
70✔
4755
            if (
4756
                $min === false
70✔
4757
                ||
4758
                $value < $min
70✔
4759
            ) {
4760
                $min = $value;
70✔
4761
            }
4762
        }
4763

4764
        return $min;
70✔
4765
    }
4766

4767
    /**
4768
     * Get the most used value from the array.
4769
     *
4770
     * @return mixed|null
4771
     *                    <p>(Immutable) Return null if there wasn't an element.</p>
4772
     *
4773
     * @phpstan-return T|null
4774
     * @psalm-mutation-free
4775
     */
4776
    public function mostUsedValue()
4777
    {
4778
        /* @phpstan-ignore return.type */
4779
        return $this->countValues()->arsortImmutable()->firstKey();
21✔
4780
    }
4781

4782
    /**
4783
     * Get the most used value from the array.
4784
     *
4785
     * @param int|null $number <p>How many values you will take?</p>
4786
     *
4787
     * @return static
4788
     *                <p>(Immutable)</p>
4789
     *
4790
     * @phpstan-return static
4791
     * @psalm-mutation-free
4792
     */
4793
    public function mostUsedValues(?int $number = null): self
4794
    {
4795
        return $this->countValues()->arsortImmutable()->firstsKeys($number);
21✔
4796
    }
4797

4798
    /**
4799
     * Move an array element to a new index.
4800
     *
4801
     * EXAMPLE: <code>
4802
     * $arr2 = new A(['A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', 'E' => 'e']);
4803
     * $newArr2 = $arr2->moveElement('D', 1); // Arrayy['A' => 'a', 'D' => 'd', 'B' => 'b', 'C' => 'c', 'E' => 'e']
4804
     * </code>
4805
     *
4806
     * @param int|string $from
4807
     * @param int        $to
4808
     *
4809
     * @return static
4810
     *                <p>(Immutable)</p>
4811
     *
4812
     * @phpstan-return static
4813
     * @psalm-mutation-free
4814
     */
4815
    public function moveElement($from, $to): self
4816
    {
4817
        $array = $this->toArray();
7✔
4818

4819
        if ((int) $from === $from) {
7✔
4820
            $tmp = \array_splice($array, $from, 1);
7✔
4821
            \array_splice($array, (int) $to, 0, $tmp);
7✔
4822
            $output = $array;
7✔
4823
        } elseif ((string) $from === $from) {
7✔
4824
            $indexToMove = \array_search($from, \array_keys($array), true);
7✔
4825
            $itemToMove = $array[$from];
7✔
4826
            if ($indexToMove !== false) {
7✔
4827
                \array_splice($array, $indexToMove, 1);
7✔
4828
            }
4829
            $i = 0;
7✔
4830
            $output = [];
7✔
4831
            foreach ($array as $key => $item) {
7✔
4832
                if ($i === $to) {
7✔
4833
                    $output[$from] = $itemToMove;
7✔
4834
                }
4835
                $output[$key] = $item;
7✔
4836
                ++$i;
7✔
4837
            }
4838
        } else {
4839
            $output = [];
×
4840
        }
4841

4842
        return static::create(
7✔
4843
            $output,
7✔
4844
            $this->iteratorClass,
7✔
4845
            false
7✔
4846
        );
7✔
4847
    }
4848

4849
    /**
4850
     * Move an array element to the first place.
4851
     *
4852
     * INFO: Instead of "Arrayy->moveElement()" this method will NOT
4853
     *       loss the keys of an indexed array.
4854
     *
4855
     * @param int|string $key
4856
     *
4857
     * @return static
4858
     *                <p>(Immutable)</p>
4859
     *
4860
     * @phpstan-return static
4861
     * @psalm-mutation-free
4862
     */
4863
    public function moveElementToFirstPlace($key): self
4864
    {
4865
        $array = $this->toArray();
7✔
4866

4867
        if ($this->offsetExists($key)) {
7✔
4868
            $tmpValue = $this->get($key);
7✔
4869
            unset($array[$key]);
7✔
4870
            $array = [$key => $tmpValue] + $array;
7✔
4871
        }
4872

4873
        return static::create(
7✔
4874
            $array,
7✔
4875
            $this->iteratorClass,
7✔
4876
            false
7✔
4877
        );
7✔
4878
    }
4879

4880
    /**
4881
     * Move an array element to the last place.
4882
     *
4883
     * INFO: Instead of "Arrayy->moveElement()" this method will NOT
4884
     *       loss the keys of an indexed array.
4885
     *
4886
     * @param int|string $key
4887
     *
4888
     * @return static
4889
     *                <p>(Immutable)</p>
4890
     *
4891
     * @phpstan-return static
4892
     * @psalm-mutation-free
4893
     */
4894
    public function moveElementToLastPlace($key): self
4895
    {
4896
        $array = $this->toArray();
7✔
4897

4898
        if ($this->offsetExists($key)) {
7✔
4899
            $tmpValue = $this->get($key);
7✔
4900
            unset($array[$key]);
7✔
4901
            $array += [$key => $tmpValue];
7✔
4902
        }
4903

4904
        return static::create(
7✔
4905
            $array,
7✔
4906
            $this->iteratorClass,
7✔
4907
            false
7✔
4908
        );
7✔
4909
    }
4910

4911
    /**
4912
     * Moves the internal iterator position to the next element and returns this element.
4913
     *
4914
     * @return false|mixed
4915
     *                     <p>(Mutable) Will return false if there are no values.</p>
4916
     *
4917
     * @phpstan-return false|T
4918
     */
4919
    public function next()
4920
    {
4921
        if ($this->generator) {
×
4922
            $this->generator->next();
×
4923

4924
            return $this->generator->current() ?? false;
×
4925
        }
4926

4927
        return \next($this->array);
×
4928
    }
4929

4930
    /**
4931
     * Get the next nth keys and values from the array.
4932
     *
4933
     * @param int $step
4934
     * @param int $offset
4935
     *
4936
     * @return static
4937
     *                <p>(Immutable)</p>
4938
     *
4939
     * @phpstan-return static
4940
     * @psalm-mutation-free
4941
     */
4942
    public function nth(int $step, int $offset = 0): self
4943
    {
4944
        $arrayFunction = function () use ($step, $offset): \Generator {
7✔
4945
            $position = 0;
7✔
4946
            foreach ($this->getGenerator() as $key => $value) {
7✔
4947
                if ($position++ % $step !== $offset) {
7✔
4948
                    continue;
7✔
4949
                }
4950

4951
                yield $key => $value;
7✔
4952
            }
4953
        };
7✔
4954

4955
        return static::create(
7✔
4956
            $arrayFunction,
7✔
4957
            $this->iteratorClass,
7✔
4958
            false
7✔
4959
        );
7✔
4960
    }
4961

4962
    /**
4963
     * Get a subset of the items from the given array.
4964
     *
4965
     * @param int[]|string[] $keys
4966
     *
4967
     * @return static
4968
     *                <p>(Immutable)</p>
4969
     *
4970
     * @phpstan-param array-key[] $keys
4971
     * @phpstan-return static
4972
     * @psalm-mutation-free
4973
     */
4974
    public function only(array $keys): self
4975
    {
4976
        $keys = \array_flip($keys);
7✔
4977

4978
        $generator = function () use ($keys): \Generator {
7✔
4979
            foreach ($this->getGenerator() as $key => $value) {
7✔
4980
                if (isset($keys[$key])) {
7✔
4981
                    yield $key => $value;
7✔
4982
                }
4983
            }
4984
        };
7✔
4985

4986
        return static::create(
7✔
4987
            $generator,
7✔
4988
            $this->iteratorClass,
7✔
4989
            false
7✔
4990
        );
7✔
4991
    }
4992

4993
    /**
4994
     * Pad array to the specified size with a given value.
4995
     *
4996
     * @param int   $size  <p>Size of the result array.</p>
4997
     * @param mixed $value <p>Empty value by default.</p>
4998
     *
4999
     * @return static
5000
     *                <p>(Immutable) Arrayy object padded to $size with $value.</p>
5001
     *
5002
     * @phpstan-return static
5003
     * @psalm-mutation-free
5004
     */
5005
    public function pad(int $size, $value): self
5006
    {
5007
        return static::create(
35✔
5008
            \array_pad($this->toArray(), $size, $value),
35✔
5009
            $this->iteratorClass,
35✔
5010
            false
35✔
5011
        );
35✔
5012
    }
5013

5014
    /**
5015
     * Partitions this array in two array according to a predicate.
5016
     * Keys are preserved in the resulting array.
5017
     *
5018
     * @param \Closure $closure
5019
     *                          <p>The predicate on which to partition.</p>
5020
     *
5021
     * @return array<int, static>
5022
     *                    <p>An array with two elements. The first element contains the array
5023
     *                    of elements where the predicate returned TRUE, the second element
5024
     *                    contains the array of elements where the predicate returned FALSE.</p>
5025
     *
5026
     * @phpstan-param \Closure(T,TKey):bool $closure
5027
     * @phpstan-return array<int, static>
5028
     */
5029
    public function partition(\Closure $closure): array
5030
    {
5031
        // init
5032
        $matches = [];
7✔
5033
        $noMatches = [];
7✔
5034

5035
        foreach ($this->getGenerator() as $key => $value) {
7✔
5036
            if ($closure($value, $key)) {
7✔
5037
                $matches[$key] = $value;
7✔
5038
            } else {
5039
                $noMatches[$key] = $value;
7✔
5040
            }
5041
        }
5042

5043
        return [self::create($matches), self::create($noMatches)];
7✔
5044
    }
5045

5046
    /**
5047
     * Pop a specified value off the end of the current array.
5048
     *
5049
     * @return mixed|null
5050
     *                    <p>(Mutable) The popped element from the current array or null if the array is e.g. empty.</p>
5051
     *
5052
     * @phpstan-return T|null
5053
     */
5054
    public function pop()
5055
    {
5056
        $this->generatorToArray();
35✔
5057

5058
        return \array_pop($this->array);
35✔
5059
    }
5060

5061
    /**
5062
     * Prepend a (key) + value to the current array.
5063
     *
5064
     * EXAMPLE: <code>
5065
     * a(['fΓ²Γ΄' => 'bΓ Ε™'])->prepend('foo'); // Arrayy[0 => 'foo', 'fΓ²Γ΄' => 'bΓ Ε™']
5066
     * </code>
5067
     *
5068
     * @param mixed $value
5069
     * @param mixed $key
5070
     *
5071
     * @return $this
5072
     *               <p>(Mutable) Return this Arrayy object, with the prepended value.</p>
5073
     *
5074
     * @phpstan-param T $value
5075
     * @phpstan-param TKey|null $key
5076
     * @phpstan-return static
5077
     */
5078
    public function prepend($value, $key = null)
5079
    {
5080
        $this->generatorToArray();
84✔
5081

5082
        if ($this->properties !== []) {
84✔
5083
            $this->checkType($key, $value);
28✔
5084
        }
5085

5086
        if ($key === null) {
70✔
5087
            \array_unshift($this->array, $value);
56✔
5088
        } else {
5089
            $this->array = [$key => $value] + $this->array;
21✔
5090
        }
5091

5092
        return $this;
70✔
5093
    }
5094

5095
    /**
5096
     * Prepend a (key) + value to the current array.
5097
     *
5098
     * EXAMPLE: <code>
5099
     * a(['fΓ²Γ΄' => 'bΓ Ε™'])->prependImmutable('foo')->getArray(); // [0 => 'foo', 'fΓ²Γ΄' => 'bΓ Ε™']
5100
     * </code>
5101
     *
5102
     * @param mixed $value
5103
     * @param mixed $key
5104
     *
5105
     * @return $this
5106
     *               <p>(Immutable) Return this Arrayy object, with the prepended value.</p>
5107
     *
5108
     * @phpstan-param T $value
5109
     * @phpstan-param TKey $key
5110
     * @phpstan-return static
5111
     * @psalm-mutation-free
5112
     */
5113
    public function prependImmutable($value, $key = null)
5114
    {
5115
        $generator = function () use ($key, $value): \Generator {
7✔
5116
            if ($this->properties !== []) {
7✔
5117
                $this->checkType($key, $value);
×
5118
            }
5119

5120
            if ($key !== null) {
7✔
5121
                yield $key => $value;
×
5122
            } else {
5123
                yield $value;
7✔
5124
            }
5125

5126
            foreach ($this->getGenerator() as $keyOld => $itemOld) {
7✔
5127
                yield $keyOld => $itemOld;
7✔
5128
            }
5129
        };
7✔
5130

5131
        return static::create(
7✔
5132
            $generator,
7✔
5133
            $this->iteratorClass,
7✔
5134
            false
7✔
5135
        );
7✔
5136
    }
5137

5138
    /**
5139
     * Add a suffix to each key.
5140
     *
5141
     * @param float|int|string $suffix
5142
     *
5143
     * @return static
5144
     *                <p>(Immutable) Return an Arrayy object, with the prepended keys.</p>
5145
     *
5146
     * @phpstan-return static
5147
     * @psalm-mutation-free
5148
     */
5149
    public function prependToEachKey($suffix): self
5150
    {
5151
        // init
5152
        $result = [];
70✔
5153

5154
        foreach ($this->getGenerator() as $key => $item) {
70✔
5155
            if ($item instanceof self) {
63✔
5156
                $result[$key] = $item->prependToEachKey($suffix);
×
5157
            } elseif (\is_array($item)) {
63✔
5158
                $result[$key] = self::create(
×
5159
                    $item,
×
5160
                    $this->iteratorClass,
×
5161
                    false
×
5162
                )->prependToEachKey($suffix)
×
5163
                    ->toArray();
×
5164
            } else {
5165
                $result[$key . $suffix] = $item;
63✔
5166
            }
5167
        }
5168

5169
        return self::create(
70✔
5170
            $result,
70✔
5171
            $this->iteratorClass,
70✔
5172
            false
70✔
5173
        );
70✔
5174
    }
5175

5176
    /**
5177
     * Add a suffix to each value.
5178
     *
5179
     * @param float|int|string $suffix
5180
     *
5181
     * @return static
5182
     *                <p>(Immutable) Return an Arrayy object, with the prepended values.</p>
5183
     *
5184
     * @phpstan-return static
5185
     * @psalm-mutation-free
5186
     */
5187
    public function prependToEachValue($suffix): self
5188
    {
5189
        // init
5190
        $result = [];
70✔
5191

5192
        foreach ($this->getGenerator() as $key => $item) {
70✔
5193
            if ($item instanceof self) {
63✔
5194
                $result[$key] = $item->prependToEachValue($suffix);
×
5195
            } elseif (\is_array($item)) {
63✔
5196
                $result[$key] = self::create(
×
5197
                    $item,
×
5198
                    $this->iteratorClass,
×
5199
                    false
×
5200
                )->prependToEachValue($suffix)
×
5201
                    ->toArray();
×
5202
            } elseif (\is_object($item) === true) {
63✔
5203
                $result[$key] = $item;
7✔
5204
            } else {
5205
                $result[$key] = $item . $suffix;
56✔
5206
            }
5207
        }
5208

5209
        return self::create(
70✔
5210
            $result,
70✔
5211
            $this->iteratorClass,
70✔
5212
            false
70✔
5213
        );
70✔
5214
    }
5215

5216
    /**
5217
     * Return the value of a given key and
5218
     * delete the key.
5219
     *
5220
     * @param int|int[]|string|string[]|null $keyOrKeys
5221
     * @param mixed                          $fallback
5222
     *
5223
     * @return mixed
5224
     *
5225
     * @template TFallback $fallback
5226
     * @phpstan-param TFallback $fallback
5227
     * @phpstan-return TFallback|T|T[]
5228
     */
5229
    public function pull($keyOrKeys = null, $fallback = null)
5230
    {
5231
        if ($keyOrKeys === null) {
42✔
5232
            $array = $this->toArray();
7✔
5233
            $this->clear();
7✔
5234

5235
            return $array;
7✔
5236
        }
5237

5238
        if (\is_array($keyOrKeys)) {
35✔
5239
            $valueOrValues = [];
7✔
5240
            foreach ($keyOrKeys as $key) {
7✔
5241
                $valueOrValues[] = $this->get($key, $fallback);
7✔
5242
                $this->offsetUnset($key);
7✔
5243
            }
5244
        } else {
5245
            $valueOrValues = $this->get($keyOrKeys, $fallback);
35✔
5246
            $this->offsetUnset($keyOrKeys);
35✔
5247
        }
5248

5249
        /** @var T|T[]|TFallback $valueOrValues */
5250
        return $valueOrValues;
35✔
5251
    }
5252

5253
    /**
5254
     * Push one or more values onto the end of array at once.
5255
     *
5256
     * @param mixed ...$args
5257
     *
5258
     * @return $this
5259
     *               <p>(Mutable) Return this Arrayy object, with pushed elements to the end of array.</p>
5260
     *
5261
     * @noinspection ReturnTypeCanBeDeclaredInspection
5262
     *
5263
     * @phpstan-param  array<TKey,T> ...$args
5264
     * @phpstan-return static
5265
     */
5266
    public function push(...$args)
5267
    {
5268
        $this->generatorToArray();
63✔
5269

5270
        if (
5271
            $this->checkPropertyTypes
63✔
5272
            &&
5273
            $this->properties !== []
63✔
5274
        ) {
5275
            foreach ($args as $key => $value) {
21✔
5276
                $this->checkType($key, $value);
21✔
5277
            }
5278
        }
5279

5280
        \array_push($this->array, ...$args); // @phpstan-ignore assign.propertyType
56✔
5281

5282
        return $this;
56✔
5283
    }
5284

5285
    /**
5286
     * Get a random value from the current array.
5287
     *
5288
     * EXAMPLE: <code>
5289
     * a([1, 2, 3, 4])->randomImmutable(2); // e.g.: Arrayy[1, 4]
5290
     * </code>
5291
     *
5292
     * @param int|null $number <p>How many values you will take?</p>
5293
     *
5294
     * @return static
5295
     *                <p>(Immutable)</p>
5296
     *
5297
     * @phpstan-return static
5298
     */
5299
    public function randomImmutable(?int $number = null): self
5300
    {
5301
        $this->generatorToArray();
133✔
5302

5303
        if ($this->count() === 0) {
133✔
5304
            return static::create(
7✔
5305
                [],
7✔
5306
                $this->iteratorClass,
7✔
5307
                false
7✔
5308
            );
7✔
5309
        }
5310

5311
        if ($number === null) {
126✔
5312
            $arrayRandValue = [$this->array[\array_rand($this->array)]];
91✔
5313

5314
            return static::create(
91✔
5315
                $arrayRandValue,
91✔
5316
                $this->iteratorClass,
91✔
5317
                false
91✔
5318
            );
91✔
5319
        }
5320

5321
        $arrayTmp = $this->array;
42✔
5322
        \shuffle($arrayTmp);
42✔
5323

5324
        return static::create(
42✔
5325
            $arrayTmp,
42✔
5326
            $this->iteratorClass,
42✔
5327
            false
42✔
5328
        )->firstsImmutable($number);
42✔
5329
    }
5330

5331
    /**
5332
     * Pick a random key/index from the keys of this array.
5333
     *
5334
     * EXAMPLE: <code>
5335
     * $arrayy = A::create([1 => 'one', 2 => 'two']);
5336
     * $arrayy->randomKey(); // e.g. 2
5337
     * </code>
5338
     *
5339
     * @throws \RangeException If array is empty
5340
     *
5341
     * @return mixed|null
5342
     *                    <p>Get a key/index or null if there wasn't a key/index.</p>
5343
     *
5344
     * @phpstan-return null|TKey
5345
     */
5346
    public function randomKey()
5347
    {
5348
        $result = $this->randomKeys(1);
28✔
5349

5350
        if (!isset($result[0])) {
28✔
5351
            $result[0] = null;
×
5352
        }
5353

5354
        return $result[0];
28✔
5355
    }
5356

5357
    /**
5358
     * Pick a given number of random keys/indexes out of this array.
5359
     *
5360
     * EXAMPLE: <code>
5361
     * a([1 => 'one', 2 => 'two'])->randomKeys(); // e.g. Arrayy[1, 2]
5362
     * </code>
5363
     *
5364
     * @param int $number <p>The number of keys/indexes (should be <= \count($this->array))</p>
5365
     *
5366
     * @throws \RangeException If array is empty
5367
     *
5368
     * @return static
5369
     *                <p>(Immutable)</p>
5370
     *
5371
     * @phpstan-return static
5372
     */
5373
    public function randomKeys(int $number): self
5374
    {
5375
        $this->generatorToArray();
91✔
5376

5377
        $count = $this->count();
91✔
5378

5379
        if (
5380
            $number === 0
91✔
5381
            ||
5382
            $number > $count
91✔
5383
        ) {
5384
            throw new \RangeException(
14✔
5385
                \sprintf(
14✔
5386
                    'Number of requested keys (%s) must be equal or lower than number of elements in this array (%s)',
14✔
5387
                    $number,
14✔
5388
                    $count
14✔
5389
                )
14✔
5390
            );
14✔
5391
        }
5392

5393
        $result = (array) \array_rand($this->array, $number);
77✔
5394

5395
        return static::create(
77✔
5396
            $result,
77✔
5397
            $this->iteratorClass,
77✔
5398
            false
77✔
5399
        );
77✔
5400
    }
5401

5402
    /**
5403
     * Get a random value from the current array.
5404
     *
5405
     * EXAMPLE: <code>
5406
     * a([1, 2, 3, 4])->randomMutable(2); // e.g.: Arrayy[1, 4]
5407
     * </code>
5408
     *
5409
     * @param int|null $number <p>How many values you will take?</p>
5410
     *
5411
     * @return $this
5412
     *               <p>(Mutable) Return this Arrayy object.</p>
5413
     *
5414
     * @phpstan-return static
5415
     */
5416
    public function randomMutable(?int $number = null): self
5417
    {
5418
        $this->generatorToArray();
119✔
5419

5420
        if ($this->count() === 0) {
119✔
5421
            return static::create(
×
5422
                [],
×
5423
                $this->iteratorClass,
×
5424
                false
×
5425
            );
×
5426
        }
5427

5428
        if ($number === null) {
119✔
5429
            $arrayRandValue = [$this->array[\array_rand($this->array)]];
49✔
5430
            $this->array = $arrayRandValue;
49✔
5431

5432
            return $this;
49✔
5433
        }
5434

5435
        \shuffle($this->array);
77✔
5436

5437
        return $this->firstsMutable($number);
77✔
5438
    }
5439

5440
    /**
5441
     * Pick a random value from the values of this array.
5442
     *
5443
     * EXAMPLE: <code>
5444
     * a([1 => 'one', 2 => 'two'])->randomValue(); // e.g. 'one'
5445
     * </code>
5446
     *
5447
     * @return mixed
5448
     *               <p>Get a random value or null if there wasn't a value.</p>
5449
     *
5450
     * @phpstan-return T|null
5451
     */
5452
    public function randomValue()
5453
    {
5454
        $result = $this->randomImmutable();
28✔
5455

5456
        if (!isset($result[0])) {
28✔
5457
            $result[0] = null;
×
5458
        }
5459

5460
        return $result[0];
28✔
5461
    }
5462

5463
    /**
5464
     * Pick a given number of random values out of this array.
5465
     *
5466
     * EXAMPLE: <code>
5467
     * a([1 => 'one', 2 => 'two'])->randomValues(); // e.g. Arrayy['one', 'two']
5468
     * </code>
5469
     *
5470
     * @param int $number
5471
     *
5472
     * @return static
5473
     *                <p>(Mutable)</p>
5474
     *
5475
     * @phpstan-return static
5476
     */
5477
    public function randomValues(int $number): self
5478
    {
5479
        return $this->randomMutable($number);
49✔
5480
    }
5481

5482
    /**
5483
     * Get a random value from an array, with the ability to skew the results.
5484
     *
5485
     * EXAMPLE: <code>
5486
     * a([0 => 3, 1 => 4])->randomWeighted([1 => 4]); // e.g.: Arrayy[4] (has a 66% chance of returning 4)
5487
     * </code>
5488
     *
5489
     * @param array    $array
5490
     * @param int|null $number <p>How many values you will take?</p>
5491
     *
5492
     * @return static
5493
     *                           <p>(Immutable)</p>
5494
     *
5495
     * @phpstan-param  array<(int&T)|(string&T),int> $array
5496
     * @phpstan-return static
5497
     */
5498
    public function randomWeighted(array $array, ?int $number = null): self
5499
    {
5500
        // init
5501
        $options = [];
63✔
5502

5503
        foreach ($array as $option => $weight) {
63✔
5504
            if ($this->searchIndex($option) !== false) {
63✔
5505
                for ($i = 0; $i < $weight; ++$i) {
14✔
5506
                    $options[] = $option;
7✔
5507
                }
5508
            }
5509
        }
5510

5511
        return $this->mergeAppendKeepIndex($options)->randomImmutable($number);
63✔
5512
    }
5513

5514
    /**
5515
     * Reduce the current array via callable e.g. anonymous-function and return the end result.
5516
     *
5517
     * EXAMPLE: <code>
5518
     * a([1, 2, 3, 4])->reduce(
5519
     *     function ($carry, $item) {
5520
     *         return $carry * $item;
5521
     *     },
5522
     *     1
5523
     * ); // Arrayy[24]
5524
     * </code>
5525
     *
5526
     * @param callable $callable
5527
     * @param mixed    $initial
5528
     *
5529
     * @return static
5530
     *                <p>(Immutable)</p>
5531
     *
5532
     * @template T2
5533
     *              <p>The output value type.</p>
5534
     *
5535
     * @phpstan-param callable(T2, T, TKey): T2 $callable
5536
     * @phpstan-param T2                        $initial
5537
     *
5538
     * @phpstan-return static
5539
     * @psalm-mutation-free
5540
     */
5541
    public function reduce($callable, $initial = []): self
5542
    {
5543
        foreach ($this->getGenerator() as $key => $value) {
126✔
5544
            $initial = $callable($initial, $value, $key);
119✔
5545
        }
5546

5547
        /** @var static $return - help for phpstan */
5548
        $return = static::create(
126✔
5549
            $initial,
126✔
5550
            $this->iteratorClass,
126✔
5551
            false
126✔
5552
        );
126✔
5553

5554
        return $return;
126✔
5555
    }
5556

5557
    /**
5558
     * @param bool $unique
5559
     *
5560
     * @return static
5561
     *                <p>(Immutable)</p>
5562
     *
5563
     * @phpstan-return static
5564
     * @psalm-mutation-free
5565
     */
5566
    public function reduce_dimension(bool $unique = true): self
5567
    {
5568
        // init
5569
        $result = [];
98✔
5570

5571
        foreach ($this->getGenerator() as $val) {
98✔
5572
            if (\is_array($val)) {
84✔
5573
                $result[] = static::create($val)->reduce_dimension($unique)->toArray();
35✔
5574
            } else {
5575
                $result[] = [$val];
84✔
5576
            }
5577
        }
5578

5579
        $result = $result === [] ? [] : \array_merge(...$result);
98✔
5580

5581
        $resultArrayy = static::create($result);
98✔
5582

5583
        /**
5584
         * @psalm-suppress ImpureMethodCall - object is already re-created
5585
         * @psalm-suppress InvalidReturnStatement - why?
5586
         */
5587
        return $unique ? $resultArrayy->unique() : $resultArrayy;
98✔
5588
    }
5589

5590
    /**
5591
     * Create a numerically re-indexed Arrayy object.
5592
     *
5593
     * EXAMPLE: <code>
5594
     * a([2 => 1, 3 => 2])->reindex(); // Arrayy[0 => 1, 1 => 2]
5595
     * </code>
5596
     *
5597
     * @return $this
5598
     *               <p>(Mutable) Return this Arrayy object, with re-indexed array-elements.</p>
5599
     *
5600
     * @phpstan-return static
5601
     */
5602
    public function reindex(): self
5603
    {
5604
        $this->generatorToArray(false);
63✔
5605

5606
        $this->array = \array_values($this->array);
63✔
5607

5608
        return $this;
63✔
5609
    }
5610

5611
    /**
5612
     * Return all items that fail the truth test.
5613
     *
5614
     * EXAMPLE: <code>
5615
     * $closure = function ($value) {
5616
     *     return $value % 2 !== 0;
5617
     * }
5618
     * a([1, 2, 3, 4])->reject($closure); // Arrayy[1 => 2, 3 => 4]
5619
     * </code>
5620
     *
5621
     * @param \Closure $closure
5622
     *
5623
     * @return static
5624
     *                <p>(Immutable)</p>
5625
     *
5626
     * @phpstan-param \Closure(T,TKey):bool  $closure
5627
     * @phpstan-return static
5628
     * @psalm-mutation-free
5629
     */
5630
    public function reject(\Closure $closure): self
5631
    {
5632
        // init
5633
        $filtered = [];
7✔
5634

5635
        foreach ($this->getGenerator() as $key => $value) {
7✔
5636
            if (!$closure($value, $key)) {
7✔
5637
                $filtered[$key] = $value;
7✔
5638
            }
5639
        }
5640

5641
        return static::create(
7✔
5642
            $filtered,
7✔
5643
            $this->iteratorClass,
7✔
5644
            false
7✔
5645
        );
7✔
5646
    }
5647

5648
    /**
5649
     * Remove a value from the current array (optional using dot-notation).
5650
     *
5651
     * EXAMPLE: <code>
5652
     * a([1 => 'bar', 'foo' => 'foo'])->remove(1); // Arrayy['foo' => 'foo']
5653
     * </code>
5654
     *
5655
     * @param mixed $key
5656
     *
5657
     * @return static
5658
     *                <p>(Mutable)</p>
5659
     *
5660
     * @phpstan-param  TKey|TKey[] $key
5661
     * @phpstan-return static
5662
     */
5663
    public function remove($key)
5664
    {
5665
        // recursive call
5666
        if (\is_array($key)) {
161✔
5667
            foreach ($key as $k) {
7✔
5668
                $this->internalRemove($k);
7✔
5669
            }
5670

5671
            return static::create(
7✔
5672
                $this->toArray(),
7✔
5673
                $this->iteratorClass,
7✔
5674
                false
7✔
5675
            );
7✔
5676
        }
5677

5678
        $this->internalRemove($key);
154✔
5679

5680
        return static::create(
154✔
5681
            $this->toArray(),
154✔
5682
            $this->iteratorClass,
154✔
5683
            false
154✔
5684
        );
154✔
5685
    }
5686

5687
    /**
5688
     * alias: for "Arrayy->removeValue()"
5689
     *
5690
     * @param mixed $element
5691
     *
5692
     * @return static
5693
     *                <p>(Immutable)</p>
5694
     *
5695
     * @phpstan-param  T $element
5696
     * @phpstan-return static
5697
     * @psalm-mutation-free
5698
     */
5699
    public function removeElement($element)
5700
    {
5701
        return $this->removeValue($element);
56✔
5702
    }
5703

5704
    /**
5705
     * Remove the first value from the current array.
5706
     *
5707
     * EXAMPLE: <code>
5708
     * a([1 => 'bar', 'foo' => 'foo'])->removeFirst(); // Arrayy['foo' => 'foo']
5709
     * </code>
5710
     *
5711
     * @return static
5712
     *                <p>(Immutable)</p>
5713
     *
5714
     * @phpstan-return static
5715
     * @psalm-mutation-free
5716
     */
5717
    public function removeFirst(): self
5718
    {
5719
        $tmpArray = $this->toArray();
49✔
5720

5721
        \array_shift($tmpArray);
49✔
5722

5723
        return static::create(
49✔
5724
            $tmpArray,
49✔
5725
            $this->iteratorClass,
49✔
5726
            false
49✔
5727
        );
49✔
5728
    }
5729

5730
    /**
5731
     * Remove the last value from the current array.
5732
     *
5733
     * EXAMPLE: <code>
5734
     * a([1 => 'bar', 'foo' => 'foo'])->removeLast(); // Arrayy[1 => 'bar']
5735
     * </code>
5736
     *
5737
     * @return static
5738
     *                <p>(Immutable)</p>
5739
     *
5740
     * @phpstan-return static
5741
     * @psalm-mutation-free
5742
     */
5743
    public function removeLast(): self
5744
    {
5745
        $tmpArray = $this->toArray();
49✔
5746

5747
        \array_pop($tmpArray);
49✔
5748

5749
        return static::create(
49✔
5750
            $tmpArray,
49✔
5751
            $this->iteratorClass,
49✔
5752
            false
49✔
5753
        );
49✔
5754
    }
5755

5756
    /**
5757
     * Removes a particular value from an array (numeric or associative).
5758
     *
5759
     * EXAMPLE: <code>
5760
     * a([1 => 'bar', 'foo' => 'foo'])->removeValue('foo'); // Arrayy[1 => 'bar']
5761
     * </code>
5762
     *
5763
     * @param mixed $value
5764
     *
5765
     * @return static
5766
     *                <p>(Immutable)</p>
5767
     *
5768
     * @phpstan-param  T $value
5769
     * @phpstan-return static
5770
     * @psalm-mutation-free
5771
     */
5772
    public function removeValue($value): self
5773
    {
5774
        $this->generatorToArray();
56✔
5775

5776
        // init
5777
        $isSequentialArray = $this->isSequential();
56✔
5778

5779
        foreach ($this->array as $key => $item) {
56✔
5780
            if ($item === $value) {
49✔
5781
                unset($this->array[$key]);
49✔
5782
            }
5783
        }
5784

5785
        if ($isSequentialArray) {
56✔
5786
            $this->array = \array_values($this->array);
42✔
5787
        }
5788

5789
        return static::create(
56✔
5790
            $this->array,
56✔
5791
            $this->iteratorClass,
56✔
5792
            false
56✔
5793
        );
56✔
5794
    }
5795

5796
    /**
5797
     * Generate array of repeated arrays.
5798
     *
5799
     * @param int $times <p>How many times has to be repeated.</p>
5800
     *
5801
     * @return static
5802
     *                <p>(Immutable)</p>
5803
     *
5804
     * @phpstan-return static
5805
     * @psalm-mutation-free
5806
     */
5807
    public function repeat($times): self
5808
    {
5809
        if ($times === 0) {
7✔
5810
            return static::create([], $this->iteratorClass);
7✔
5811
        }
5812

5813
        return static::create(
7✔
5814
            \array_fill(0, (int) $times, $this->toArray()),
7✔
5815
            $this->iteratorClass,
7✔
5816
            false
7✔
5817
        );
7✔
5818
    }
5819

5820
    /**
5821
     * Replace a key with a new key/value pair.
5822
     *
5823
     * EXAMPLE: <code>
5824
     * $arrayy = a([1 => 'foo', 2 => 'foo2', 3 => 'bar']);
5825
     * $arrayy->replace(2, 'notfoo', 'notbar'); // Arrayy[1 => 'foo', 'notfoo' => 'notbar', 3 => 'bar']
5826
     * </code>
5827
     *
5828
     * @param mixed $oldKey
5829
     * @param mixed $newKey
5830
     * @param mixed $newValue
5831
     *
5832
     * @return static
5833
     *                <p>(Immutable)</p>
5834
     *
5835
     * @phpstan-param TKey $oldKey
5836
     * @phpstan-param TKey $newKey
5837
     * @phpstan-param T $newValue
5838
     * @phpstan-return static
5839
     * @psalm-mutation-free
5840
     */
5841
    public function replace($oldKey, $newKey, $newValue): self
5842
    {
5843
        $that = clone $this;
35✔
5844

5845
        /**
5846
         * @psalm-suppress ImpureMethodCall - object is already cloned
5847
         */
5848
        return $that->remove($oldKey)
35✔
5849
            ->set($newKey, $newValue);
35✔
5850
    }
5851

5852
    /**
5853
     * Create an array using the current array as values and the other array as keys.
5854
     *
5855
     * EXAMPLE: <code>
5856
     * $firstArray = [
5857
     *     1 => 'one',
5858
     *     2 => 'two',
5859
     *     3 => 'three',
5860
     * ];
5861
     * $secondArray = [
5862
     *     'one' => 1,
5863
     *     1     => 'one',
5864
     *     2     => 2,
5865
     * ];
5866
     * $arrayy = a($firstArray);
5867
     * $arrayy->replaceAllKeys($secondArray); // Arrayy[1 => "one", 'one' => "two", 2 => "three"]
5868
     * </code>
5869
     *
5870
     * @param int[]|string[] $keys <p>An array of keys.</p>
5871
     *
5872
     * @return static
5873
     *                <p>(Immutable) Arrayy object with keys from the other array, empty Arrayy object if the number of elements
5874
     *                for each array isn't equal or if the arrays are empty.
5875
     *                </p>
5876
     *
5877
     * @phpstan-param  array<TKey> $keys
5878
     * @phpstan-return static
5879
     * @psalm-mutation-free
5880
     */
5881
    public function replaceAllKeys(array $keys): self
5882
    {
5883
        $data = \array_combine($keys, $this->toArray());
14✔
5884
        /* @phpstan-ignore identical.alwaysFalse */
5885
        if ($data === false) {
14✔
5886
            $data = [];
×
5887
        }
5888

5889
        return static::create(
14✔
5890
            $data,
14✔
5891
            $this->iteratorClass,
14✔
5892
            false
14✔
5893
        );
14✔
5894
    }
5895

5896
    /**
5897
     * Create an array using the current array as keys and the other array as values.
5898
     *
5899
     * EXAMPLE: <code>
5900
     * $firstArray = [
5901
     *     1 => 'one',
5902
     *     2 => 'two',
5903
     *     3 => 'three',
5904
     * ];
5905
     * $secondArray = [
5906
     *     'one' => 1,
5907
     *     1     => 'one',
5908
     *     2     => 2,
5909
     * ];
5910
     * $arrayy = a($firstArray);
5911
     * $arrayy->replaceAllValues($secondArray); // Arrayy['one' => 1, 'two' => 'one', 'three' => 2]
5912
     * </code>
5913
     *
5914
     * @param array $array <p>An array of values.</p>
5915
     *
5916
     * @return static
5917
     *                <p>(Immutable) Arrayy object with values from the other array, empty Arrayy object if the number of elements
5918
     *                for each array isn't equal or if the arrays are empty.
5919
     *                </p>
5920
     *
5921
     * @phpstan-param  array<T> $array
5922
     * @phpstan-return static
5923
     * @psalm-mutation-free
5924
     */
5925
    public function replaceAllValues(array $array): self
5926
    {
5927
        $data = \array_combine($this->toArray(), $array);
14✔
5928
        /* @phpstan-ignore identical.alwaysFalse */
5929
        if ($data === false) {
14✔
5930
            $data = [];
×
5931
        }
5932

5933
        return static::create(
14✔
5934
            $data,
14✔
5935
            $this->iteratorClass,
14✔
5936
            false
14✔
5937
        );
14✔
5938
    }
5939

5940
    /**
5941
     * Replace the keys in an array with another set.
5942
     *
5943
     * EXAMPLE: <code>
5944
     * a([1 => 'bar', 'foo' => 'foo'])->replaceKeys([1 => 2, 'foo' => 'replaced']); // Arrayy[2 => 'bar', 'replaced' => 'foo']
5945
     * </code>
5946
     *
5947
     * @param array $keys <p>An array of keys matching the array's size.</p>
5948
     *
5949
     * @return static
5950
     *                <p>(Immutable)</p>
5951
     *
5952
     * @phpstan-param  array<TKey> $keys
5953
     * @phpstan-return static
5954
     * @psalm-mutation-free
5955
     */
5956
    public function replaceKeys(array $keys): self
5957
    {
5958
        $values = \array_values($this->toArray());
7✔
5959
        $result = \array_combine($keys, $values);
7✔
5960
        /* @phpstan-ignore identical.alwaysFalse */
5961
        if ($result === false) {
7✔
5962
            $result = [];
×
5963
        }
5964

5965
        return static::create(
7✔
5966
            $result,
7✔
5967
            $this->iteratorClass,
7✔
5968
            false
7✔
5969
        );
7✔
5970
    }
5971

5972
    /**
5973
     * Replace the first matched value in an array.
5974
     *
5975
     * EXAMPLE: <code>
5976
     * $testArray = ['bar', 'foo' => 'foo', 'foobar' => 'foobar'];
5977
     * a($testArray)->replaceOneValue('foo', 'replaced'); // Arrayy['bar', 'foo' => 'replaced', 'foobar' => 'foobar']
5978
     * </code>
5979
     *
5980
     * @param mixed $search      <p>The value to replace.</p>
5981
     * @param mixed $replacement <p>The value to replace.</p>
5982
     *
5983
     * @return static
5984
     *                <p>(Immutable)</p>
5985
     *
5986
     * @phpstan-param T $search
5987
     * @phpstan-param T $replacement
5988
     * @phpstan-return static
5989
     * @psalm-mutation-free
5990
     */
5991
    public function replaceOneValue($search, $replacement = ''): self
5992
    {
5993
        $array = $this->toArray();
21✔
5994
        $key = \array_search($search, $array, true);
21✔
5995

5996
        if ($key !== false) {
21✔
5997
            $array[$key] = $replacement;
21✔
5998
        }
5999

6000
        return static::create(
21✔
6001
            $array,
21✔
6002
            $this->iteratorClass,
21✔
6003
            false
21✔
6004
        );
21✔
6005
    }
6006

6007
    /**
6008
     * Replace values in the current array.
6009
     *
6010
     * EXAMPLE: <code>
6011
     * $testArray = ['bar', 'foo' => 'foo', 'foobar' => 'foobar'];
6012
     * a($testArray)->replaceValues('foo', 'replaced'); // Arrayy['bar', 'foo' => 'replaced', 'foobar' => 'replacedbar']
6013
     * </code>
6014
     *
6015
     * @param string $search      <p>The value to replace.</p>
6016
     * @param string $replacement <p>What to replace it with.</p>
6017
     *
6018
     * @return static
6019
     *                <p>(Immutable)</p>
6020
     *
6021
     * @phpstan-return static
6022
     * @psalm-mutation-free
6023
     */
6024
    public function replaceValues($search, $replacement = ''): self
6025
    {
6026
        $callable = static function ($value) use ($search, $replacement) {
7✔
6027
            return \str_replace($search, $replacement, $value);
7✔
6028
        };
7✔
6029

6030
        /* @phpstan-ignore argument.type */
6031
        return $this->each($callable);
7✔
6032
    }
6033

6034
    /**
6035
     * Get the last elements from index $from until the end of this array.
6036
     *
6037
     * EXAMPLE: <code>
6038
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->rest(2); // Arrayy[0 => 'lall']
6039
     * </code>
6040
     *
6041
     * @param int $from
6042
     *
6043
     * @return static
6044
     *                <p>(Immutable)</p>
6045
     *
6046
     * @phpstan-return static
6047
     * @psalm-mutation-free
6048
     */
6049
    public function rest(int $from = 1): self
6050
    {
6051
        $tmpArray = $this->toArray();
105✔
6052

6053
        return static::create(
105✔
6054
            \array_splice($tmpArray, $from),
105✔
6055
            $this->iteratorClass,
105✔
6056
            false
105✔
6057
        );
105✔
6058
    }
6059

6060
    /**
6061
     * Return the array in the reverse order.
6062
     *
6063
     * EXAMPLE: <code>
6064
     * a([1 => 1, 2 => 2, 3 => 3])->reverse(); // self[3, 2, 1]
6065
     * </code>
6066
     *
6067
     * @return $this
6068
     *               <p>(Mutable) Return this Arrayy object.</p>
6069
     *
6070
     * @phpstan-return static
6071
     */
6072
    public function reverse(): self
6073
    {
6074
        $this->generatorToArray();
63✔
6075

6076
        $this->array = \array_reverse($this->array);
63✔
6077

6078
        return $this;
63✔
6079
    }
6080

6081
    /**
6082
     * Return the array with keys in the reverse order.
6083
     *
6084
     * EXAMPLE: <code>
6085
     * a([1 => 1, 2 => 2, 3 => 3])->reverse(); // self[3 => 3, 2 => 2, 1 => 1]
6086
     * </code>
6087
     *
6088
     * @return $this
6089
     *               <p>(Mutable) Return this Arrayy object.</p>
6090
     *
6091
     * @phpstan-return static
6092
     */
6093
    public function reverseKeepIndex(): self
6094
    {
6095
        $this->generatorToArray();
35✔
6096

6097
        $this->array = \array_reverse($this->array, true);
35✔
6098

6099
        return $this;
35✔
6100
    }
6101

6102
    /**
6103
     * Sort an array in reverse order.
6104
     *
6105
     * @param int $sort_flags [optional] <p>
6106
     *                        You may modify the behavior of the sort using the optional
6107
     *                        parameter sort_flags, for details
6108
     *                        see sort.
6109
     *                        </p>
6110
     *
6111
     * @return $this
6112
     *               <p>(Mutable) Return this Arrayy object.</p>
6113
     *
6114
     * @phpstan-return static
6115
     */
6116
    public function rsort(int $sort_flags = 0): self
6117
    {
6118
        $this->generatorToArray();
28✔
6119

6120
        \rsort($this->array, $sort_flags);
28✔
6121

6122
        return $this;
28✔
6123
    }
6124

6125
    /**
6126
     * Sort an array in reverse order.
6127
     *
6128
     * @param int $sort_flags [optional] <p>
6129
     *                        You may modify the behavior of the sort using the optional
6130
     *                        parameter sort_flags, for details
6131
     *                        see sort.
6132
     *                        </p>
6133
     *
6134
     * @return $this
6135
     *               <p>(Immutable) Return this Arrayy object.</p>
6136
     *
6137
     * @phpstan-return static
6138
     * @psalm-mutation-free
6139
     */
6140
    public function rsortImmutable(int $sort_flags = 0): self
6141
    {
6142
        $that = clone $this;
28✔
6143

6144
        /**
6145
         * @psalm-suppress ImpureMethodCall - object is already cloned
6146
         */
6147
        $that->rsort($sort_flags);
28✔
6148

6149
        return $that;
28✔
6150
    }
6151

6152
    /**
6153
     * Search for the first index of the current array via $value.
6154
     *
6155
     * EXAMPLE: <code>
6156
     * a(['fΓ²Γ΄' => 'bΓ Ε™', 'lall' => 'bΓ Ε™'])->searchIndex('bΓ Ε™'); // Arrayy[0 => 'fΓ²Γ΄']
6157
     * </code>
6158
     *
6159
     * @param mixed $value
6160
     *
6161
     * @return false|int|string
6162
     *                          <p>Will return <b>FALSE</b> if the value can't be found.</p>
6163
     *
6164
     * @phpstan-param T $value
6165
     * @phpstan-return false|TKey
6166
     *
6167
     * @psalm-mutation-free
6168
     */
6169
    public function searchIndex($value)
6170
    {
6171
        foreach ($this->getGenerator() as $keyFromArray => $valueFromArray) {
147✔
6172
            if ($value === $valueFromArray) {
140✔
6173
                return $keyFromArray;
70✔
6174
            }
6175
        }
6176

6177
        return false;
77✔
6178
    }
6179

6180
    /**
6181
     * Search for the value of the current array via $index.
6182
     *
6183
     * EXAMPLE: <code>
6184
     * a(['fΓ²Γ΄' => 'bΓ Ε™'])->searchValue('fΓ²Γ΄'); // Arrayy[0 => 'bΓ Ε™']
6185
     * </code>
6186
     *
6187
     * @param mixed $index
6188
     *
6189
     * @return static
6190
     *                <p>(Immutable) Will return a empty Arrayy if the value wasn't found.</p>
6191
     *
6192
     * @phpstan-param TKey $index
6193
     * @phpstan-return static
6194
     * @psalm-mutation-free
6195
     */
6196
    public function searchValue($index): self
6197
    {
6198
        $this->generatorToArray();
63✔
6199

6200
        // init
6201
        $return = [];
63✔
6202

6203
        if ($this->array === []) {
63✔
6204
            return static::create(
×
6205
                [],
×
6206
                $this->iteratorClass,
×
6207
                false
×
6208
            );
×
6209
        }
6210

6211
        // php cast "bool"-index into "int"-index
6212
        /* @phpstan-ignore identical.alwaysFalse */
6213
        if ((bool) $index === $index) {
63✔
6214
            $index = (int) $index;
7✔
6215
        }
6216

6217
        if ($this->offsetExists($index)) {
63✔
6218
            $return = [$this->array[$index]];
49✔
6219
        }
6220

6221
        return static::create(
63✔
6222
            $return,
63✔
6223
            $this->iteratorClass,
63✔
6224
            false
63✔
6225
        );
63✔
6226
    }
6227

6228
    /**
6229
     * Set a value for the current array (optional using dot-notation).
6230
     *
6231
     * EXAMPLE: <code>
6232
     * $arrayy = a(['Lars' => ['lastname' => 'Moelleken']]);
6233
     * $arrayy->set('Lars.lastname', 'MΓΌller'); // Arrayy['Lars', ['lastname' => 'MΓΌller']]]
6234
     * </code>
6235
     *
6236
     * @param string $key   <p>The key to set.</p>
6237
     * @param mixed  $value <p>Its value.</p>
6238
     *
6239
     * @return $this
6240
     *               <p>(Mutable) Return this Arrayy object.</p>
6241
     *
6242
     * @phpstan-param  TKey $key
6243
     * @phpstan-param  T $value
6244
     * @phpstan-return static
6245
     */
6246
    public function set($key, $value): self
6247
    {
6248
        $this->internalSet($key, $value);
203✔
6249

6250
        return $this;
196✔
6251
    }
6252

6253
    /**
6254
     * Get a value from a array and set it if it was not.
6255
     *
6256
     * WARNING: this method only set the value, if the $key is not already set
6257
     *
6258
     * EXAMPLE: <code>
6259
     * $arrayy = a([1 => 1, 2 => 2, 3 => 3]);
6260
     * $arrayy->setAndGet(1, 4); // 1
6261
     * $arrayy->setAndGet(0, 4); // 4
6262
     * </code>
6263
     *
6264
     * @param mixed $key      <p>The key</p>
6265
     * @param mixed $fallback <p>The default value to set if it isn't.</p>
6266
     *
6267
     * @return mixed
6268
     *               <p>(Mutable)</p>
6269
     *
6270
     * @phpstan-param TKey $key
6271
     * @phpstan-param T $fallback
6272
     */
6273
    public function setAndGet($key, $fallback = null)
6274
    {
6275
        $this->generatorToArray();
77✔
6276

6277
        // If the key doesn't exist, set it.
6278
        if (!$this->has($key)) {
77✔
6279
            $this->array = $this->set($key, $fallback)->toArray();
28✔
6280
        }
6281

6282
        return $this->get($key);
77✔
6283
    }
6284

6285
    /**
6286
     * Shifts a specified value off the beginning of array.
6287
     *
6288
     * @return mixed|null
6289
     *                    <p>(Mutable) A shifted element from the current array.</p>
6290
     *
6291
     * @phpstan-return T|null
6292
     */
6293
    public function shift()
6294
    {
6295
        $this->generatorToArray();
35✔
6296

6297
        return \array_shift($this->array);
35✔
6298
    }
6299

6300
    /**
6301
     * Shuffle the current array.
6302
     *
6303
     * EXAMPLE: <code>
6304
     * a([1 => 'bar', 'foo' => 'foo'])->shuffle(); // e.g.: Arrayy[['foo' => 'foo', 1 => 'bar']]
6305
     * </code>
6306
     *
6307
     * @param bool       $secure <p>using a CSPRNG | @see https://paragonie.com/b/JvICXzh_jhLyt4y3</p>
6308
     * @param array|null $array  [optional]
6309
     *
6310
     * @return static
6311
     *                <p>(Immutable)</p>
6312
     *
6313
     * @phpstan-param  array<TKey,T> $array
6314
     * @phpstan-return static
6315
     */
6316
    public function shuffle(bool $secure = false, ?array $array = null): self
6317
    {
6318
        if ($array === null) {
14✔
6319
            $array = $this->toArray(false);
14✔
6320
        }
6321

6322
        if ($secure !== true) {
14✔
6323
            \shuffle($array);
14✔
6324
        } else {
6325
            $size = \count($array, \COUNT_NORMAL);
7✔
6326
            $keys = \array_keys($array);
7✔
6327
            for ($i = $size - 1; $i > 0; --$i) {
7✔
6328
                try {
6329
                    $r = \random_int(0, $i);
7✔
6330
                } catch (\Exception $e) {
×
6331
                    /** @noinspection RandomApiMigrationInspection - "random_int" is already in use */
6332
                    $r = \mt_rand(0, $i);
×
6333
                }
6334
                if ($r !== $i) {
7✔
6335
                    $temp = $array[$keys[$r]];
6✔
6336
                    $array[$keys[$r]] = $array[$keys[$i]];
6✔
6337
                    $array[$keys[$i]] = $temp;
6✔
6338
                }
6339
            }
6340
        }
6341

6342
        foreach ($array as $key => $value) {
14✔
6343
            // check if recursive is needed
6344
            if (\is_array($value)) {
14✔
6345
                /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
6346
                /** @phpstan-var array<TKey,T> $value */
6347
                $value = $value;
×
6348

6349
                $array[$key] = $this->shuffle($secure, $value);
×
6350
            }
6351
        }
6352

6353
        return static::create(
14✔
6354
            $array,
14✔
6355
            $this->iteratorClass,
14✔
6356
            false
14✔
6357
        );
14✔
6358
    }
6359

6360
    /**
6361
     * Count the values from the current array.
6362
     *
6363
     * alias: for "Arrayy->count()"
6364
     *
6365
     * @param int $mode
6366
     *
6367
     * @return int
6368
     */
6369
    public function size(int $mode = \COUNT_NORMAL): int
6370
    {
6371
        return $this->count($mode);
140✔
6372
    }
6373

6374
    /**
6375
     * Checks whether array has exactly $size items.
6376
     *
6377
     * @param int $size
6378
     *
6379
     * @return bool
6380
     */
6381
    public function sizeIs(int $size): bool
6382
    {
6383
        // init
6384
        $itemsTempCount = 0;
7✔
6385

6386
        /** @noinspection PhpUnusedLocalVariableInspection */
6387
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
6388
        foreach ($this->getGeneratorByReference() as &$value) {
7✔
6389
            ++$itemsTempCount;
7✔
6390
            if ($itemsTempCount > $size) {
7✔
6391
                return false;
7✔
6392
            }
6393
        }
6394

6395
        return $itemsTempCount === $size;
7✔
6396
    }
6397

6398
    /**
6399
     * Checks whether array has between $fromSize to $toSize items. $toSize can be
6400
     * smaller than $fromSize.
6401
     *
6402
     * @param int $fromSize
6403
     * @param int $toSize
6404
     *
6405
     * @return bool
6406
     */
6407
    public function sizeIsBetween(int $fromSize, int $toSize): bool
6408
    {
6409
        if ($fromSize > $toSize) {
7✔
6410
            $tmp = $toSize;
7✔
6411
            $toSize = $fromSize;
7✔
6412
            $fromSize = $tmp;
7✔
6413
        }
6414

6415
        // init
6416
        $itemsTempCount = 0;
7✔
6417

6418
        /** @noinspection PhpUnusedLocalVariableInspection */
6419
        foreach ($this->getGenerator() as $value) {
7✔
6420
            ++$itemsTempCount;
7✔
6421
            if ($itemsTempCount > $toSize) {
7✔
6422
                return false;
7✔
6423
            }
6424
        }
6425

6426
        return $fromSize < $itemsTempCount && $itemsTempCount < $toSize;
7✔
6427
    }
6428

6429
    /**
6430
     * Checks whether array has more than $size items.
6431
     *
6432
     * @param int $size
6433
     *
6434
     * @return bool
6435
     */
6436
    public function sizeIsGreaterThan(int $size): bool
6437
    {
6438
        // init
6439
        $itemsTempCount = 0;
7✔
6440

6441
        /** @noinspection PhpUnusedLocalVariableInspection */
6442
        foreach ($this->getGenerator() as $value) {
7✔
6443
            ++$itemsTempCount;
7✔
6444
            if ($itemsTempCount > $size) {
7✔
6445
                return true;
7✔
6446
            }
6447
        }
6448

6449
        return $itemsTempCount > $size;
7✔
6450
    }
6451

6452
    /**
6453
     * Checks whether array has less than $size items.
6454
     *
6455
     * @param int $size
6456
     *
6457
     * @return bool
6458
     */
6459
    public function sizeIsLessThan(int $size): bool
6460
    {
6461
        // init
6462
        $itemsTempCount = 0;
7✔
6463

6464
        /** @noinspection PhpUnusedLocalVariableInspection */
6465
        foreach ($this->getGenerator() as $value) {
7✔
6466
            ++$itemsTempCount;
7✔
6467
            if ($itemsTempCount > $size) {
7✔
6468
                return false;
7✔
6469
            }
6470
        }
6471

6472
        return $itemsTempCount < $size;
7✔
6473
    }
6474

6475
    /**
6476
     * Counts all elements in an array, or something in an object.
6477
     *
6478
     * <p>
6479
     * For objects, if you have SPL installed, you can hook into count() by implementing interface {@see Countable}.
6480
     * The interface has exactly one method, {@see Countable::count()}, which returns the return value for the count()
6481
     * function. Please see the {@see Array} section of the manual for a detailed explanation of how arrays are
6482
     * implemented and used in PHP.
6483
     * </p>
6484
     *
6485
     * @return int
6486
     *             <p>
6487
     *             The number of elements in var, which is
6488
     *             typically an array, since anything else will have one
6489
     *             element.
6490
     *             </p>
6491
     *             <p>
6492
     *             If var is not an array or an object with
6493
     *             implemented Countable interface,
6494
     *             1 will be returned.
6495
     *             There is one exception, if var is &null;,
6496
     *             0 will be returned.
6497
     *             </p>
6498
     *             <p>
6499
     *             Caution: count may return 0 for a variable that isn't set,
6500
     *             but it may also return 0 for a variable that has been initialized with an
6501
     *             empty array. Use isset to test if a variable is set.
6502
     *             </p>
6503
     */
6504
    public function sizeRecursive(): int
6505
    {
6506
        return \count($this->toArray(), \COUNT_RECURSIVE);
70✔
6507
    }
6508

6509
    /**
6510
     * Extract a slice of the array.
6511
     *
6512
     * @param int      $offset       <p>Slice begin index.</p>
6513
     * @param int|null $length       <p>Length of the slice.</p>
6514
     * @param bool     $preserveKeys <p>Whether array keys are preserved or no.</p>
6515
     *
6516
     * @return static
6517
     *                <p>(Immutable) A slice of the original array with length $length.</p>
6518
     *
6519
     * @phpstan-return static
6520
     * @psalm-mutation-free
6521
     */
6522
    public function slice(int $offset, ?int $length = null, bool $preserveKeys = false)
6523
    {
6524
        return static::create(
35✔
6525
            \array_slice(
35✔
6526
                $this->toArray(),
35✔
6527
                $offset,
35✔
6528
                $length,
35✔
6529
                $preserveKeys
35✔
6530
            ),
35✔
6531
            $this->iteratorClass,
35✔
6532
            false
35✔
6533
        );
35✔
6534
    }
6535

6536
    /**
6537
     * Sort the current array and optional you can keep the keys.
6538
     *
6539
     * EXAMPLE: <code>
6540
     * a(3 => 'd', 2 => 'f', 0 => 'a')->sort(SORT_ASC, SORT_NATURAL, false); // Arrayy[0 => 'a', 1 => 'd', 2 => 'f']
6541
     * </code>
6542
     *
6543
     * @param int|string $direction
6544
     *                              <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6545
     * @param int        $strategy
6546
     *                              <p>sort_flags => use e.g.: <strong>SORT_REGULAR</strong> (default) or
6547
     *                              <strong>SORT_NATURAL</strong></p>
6548
     * @param bool       $keepKeys
6549
     *
6550
     * @return static
6551
     *                <p>(Mutable) Return this Arrayy object.</p>
6552
     *
6553
     * @phpstan-return static
6554
     */
6555
    public function sort(
6556
        $direction = \SORT_ASC,
6557
        int $strategy = \SORT_REGULAR,
6558
        bool $keepKeys = false
6559
    ): self {
6560
        $this->generatorToArray();
140✔
6561

6562
        return $this->sorting(
140✔
6563
            $this->array,
140✔
6564
            $direction,
140✔
6565
            $strategy,
140✔
6566
            $keepKeys
140✔
6567
        );
140✔
6568
    }
6569

6570
    /**
6571
     * Sort the current array and optional you can keep the keys.
6572
     *
6573
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6574
     * @param int        $strategy  <p>sort_flags => use e.g.: <strong>SORT_REGULAR</strong> (default) or
6575
     *                              <strong>SORT_NATURAL</strong></p>
6576
     * @param bool       $keepKeys
6577
     *
6578
     * @return static
6579
     *                <p>(Immutable) Return this Arrayy object.</p>
6580
     *
6581
     * @phpstan-return static
6582
     */
6583
    public function sortImmutable(
6584
        $direction = \SORT_ASC,
6585
        int $strategy = \SORT_REGULAR,
6586
        bool $keepKeys = false
6587
    ): self {
6588
        $that = clone $this;
84✔
6589

6590
        $that->generatorToArray();
84✔
6591

6592
        return $that->sorting(
84✔
6593
            $that->array,
84✔
6594
            $direction,
84✔
6595
            $strategy,
84✔
6596
            $keepKeys
84✔
6597
        );
84✔
6598
    }
6599

6600
    /**
6601
     * Sort the current array by key.
6602
     *
6603
     * EXAMPLE: <code>
6604
     * a([1 => 2, 0 => 1])->sortKeys(\SORT_ASC); // Arrayy[0 => 1, 1 => 2]
6605
     * </code>
6606
     *
6607
     * @see http://php.net/manual/en/function.ksort.php
6608
     * @see http://php.net/manual/en/function.krsort.php
6609
     *
6610
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6611
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6612
     *                              <strong>SORT_NATURAL</strong></p>
6613
     *
6614
     * @return $this
6615
     *               <p>(Mutable) Return this Arrayy object.</p>
6616
     *
6617
     * @phpstan-return static
6618
     */
6619
    public function sortKeys(
6620
        $direction = \SORT_ASC,
6621
        int $strategy = \SORT_REGULAR
6622
    ): self {
6623
        $this->generatorToArray();
126✔
6624

6625
        $this->sorterKeys($this->array, $direction, $strategy);
126✔
6626

6627
        return $this;
126✔
6628
    }
6629

6630
    /**
6631
     * Sort the current array by key.
6632
     *
6633
     * @see          http://php.net/manual/en/function.ksort.php
6634
     * @see          http://php.net/manual/en/function.krsort.php
6635
     *
6636
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6637
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6638
     *                              <strong>SORT_NATURAL</strong></p>
6639
     *
6640
     * @return $this
6641
     *               <p>(Immutable) Return this Arrayy object.</p>
6642
     *
6643
     * @phpstan-return static
6644
     * @psalm-mutation-free
6645
     */
6646
    public function sortKeysImmutable(
6647
        $direction = \SORT_ASC,
6648
        int $strategy = \SORT_REGULAR
6649
    ): self {
6650
        $that = clone $this;
56✔
6651

6652
        /**
6653
         * @psalm-suppress ImpureMethodCall - object is already cloned
6654
         */
6655
        $that->sortKeys($direction, $strategy);
56✔
6656

6657
        return $that;
56✔
6658
    }
6659

6660
    /**
6661
     * Sort the current array by value.
6662
     *
6663
     * EXAMPLE: <code>
6664
     * a(3 => 'd', 2 => 'f', 0 => 'a')->sortValueKeepIndex(SORT_ASC, SORT_REGULAR); // Arrayy[0 => 'a', 3 => 'd', 2 => 'f']
6665
     * </code>
6666
     *
6667
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6668
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6669
     *                              <strong>SORT_NATURAL</strong></p>
6670
     *
6671
     * @return static
6672
     *                <p>(Mutable)</p>
6673
     *
6674
     * @phpstan-return static
6675
     */
6676
    public function sortValueKeepIndex(
6677
        $direction = \SORT_ASC,
6678
        int $strategy = \SORT_REGULAR
6679
    ): self {
6680
        return $this->sort($direction, $strategy, true);
7✔
6681
    }
6682

6683
    /**
6684
     * Sort the current array by value.
6685
     *
6686
     * EXAMPLE: <code>
6687
     * a(3 => 'd', 2 => 'f', 0 => 'a')->sortValueNewIndex(SORT_ASC, SORT_NATURAL); // Arrayy[0 => 'a', 1 => 'd', 2 => 'f']
6688
     * </code>
6689
     *
6690
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6691
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6692
     *                              <strong>SORT_NATURAL</strong></p>
6693
     *
6694
     * @return static
6695
     *                <p>(Mutable)</p>
6696
     *
6697
     * @phpstan-return static
6698
     */
6699
    public function sortValueNewIndex($direction = \SORT_ASC, int $strategy = \SORT_REGULAR): self
6700
    {
6701
        return $this->sort($direction, $strategy, false);
7✔
6702
    }
6703

6704
    /**
6705
     * Sort a array by value or by a closure.
6706
     *
6707
     * - If the sorter is null, the array is sorted naturally.
6708
     * - Associative (string) keys will be maintained, but numeric keys will be re-indexed.
6709
     *
6710
     * EXAMPLE: <code>
6711
     * $testArray = range(1, 5);
6712
     * $under = a($testArray)->sorter(
6713
     *     function ($value) {
6714
     *         return $value % 2 === 0;
6715
     *     }
6716
     * );
6717
     * var_dump($under); // Arrayy[1, 3, 5, 2, 4]
6718
     * </code>
6719
     *
6720
     * @param callable|mixed|null $sorter
6721
     * @param int|string          $direction <p>use <strong>SORT_ASC</strong> (default) or
6722
     *                                       <strong>SORT_DESC</strong></p>
6723
     * @param int                 $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6724
     *                                       <strong>SORT_NATURAL</strong></p>
6725
     *
6726
     * @return static
6727
     *                <p>(Immutable)</p>
6728
     *
6729
     * @pslam-param callable|T|null $sorter
6730
     * @phpstan-return static
6731
     * @psalm-mutation-free
6732
     */
6733
    public function sorter($sorter = null, $direction = \SORT_ASC, int $strategy = \SORT_REGULAR): self
6734
    {
6735
        $array = $this->toArray();
7✔
6736
        $direction = $this->getDirection($direction);
7✔
6737

6738
        // Transform all values into their results.
6739
        if ($sorter) {
7✔
6740
            $arrayy = static::create(
7✔
6741
                $array,
7✔
6742
                $this->iteratorClass,
7✔
6743
                false
7✔
6744
            );
7✔
6745

6746
            /**
6747
             * @psalm-suppress MissingClosureReturnType
6748
             * @psalm-suppress MissingClosureParamType
6749
             */
6750
            $results = $arrayy->each(
7✔
6751
                static function ($value) use ($sorter) {
7✔
6752
                    if (\is_callable($sorter) === true) {
7✔
6753
                        return $sorter($value);
7✔
6754
                    }
6755

6756
                    return $sorter === $value;
7✔
6757
                }
7✔
6758
            );
7✔
6759

6760
            $results = $results->toArray();
7✔
6761
        } else {
6762
            $results = $array;
7✔
6763
        }
6764

6765
        // Sort by the results and replace by original values
6766
        \array_multisort($results, $direction, $strategy, $array);
7✔
6767

6768
        return static::create(
7✔
6769
            $array,
7✔
6770
            $this->iteratorClass,
7✔
6771
            false
7✔
6772
        );
7✔
6773
    }
6774

6775
    /**
6776
     * @param int      $offset
6777
     * @param int|null $length
6778
     * @param array    $replacement
6779
     *
6780
     * @return static
6781
     *                <p>(Immutable)</p>
6782
     *
6783
     * @phpstan-param  array<T> $replacement
6784
     * @phpstan-return static
6785
     * @psalm-mutation-free
6786
     */
6787
    public function splice(int $offset, ?int $length = null, $replacement = []): self
6788
    {
6789
        $tmpArray = $this->toArray();
7✔
6790

6791
        \array_splice(
7✔
6792
            $tmpArray,
7✔
6793
            $offset,
7✔
6794
            $length ?? $this->count(),
7✔
6795
            $replacement
7✔
6796
        );
7✔
6797

6798
        return static::create(
7✔
6799
            $tmpArray,
7✔
6800
            $this->iteratorClass,
7✔
6801
            false
7✔
6802
        );
7✔
6803
    }
6804

6805
    /**
6806
     * Split an array in the given amount of pieces.
6807
     *
6808
     * EXAMPLE: <code>
6809
     * a(['a' => 1, 'b' => 2])->split(2, true); // Arrayy[['a' => 1], ['b' => 2]]
6810
     * </code>
6811
     *
6812
     * @param int  $numberOfPieces
6813
     * @param bool $keepKeys
6814
     *
6815
     * @return static
6816
     *                <p>(Immutable)</p>
6817
     *
6818
     * @phpstan-return static
6819
     * @psalm-mutation-free
6820
     */
6821
    public function split(int $numberOfPieces = 2, bool $keepKeys = false): self
6822
    {
6823
        if ($keepKeys) {
7✔
6824
            $generator = function () use ($numberOfPieces) {
7✔
6825
                $carry = [];
7✔
6826
                $i = 1;
7✔
6827
                foreach ($this->getGenerator() as $key => $value) {
7✔
6828
                    $carry[$key] = $value;
7✔
6829

6830
                    if ($i % $numberOfPieces !== 0) {
7✔
6831
                        ++$i;
7✔
6832

6833
                        continue;
7✔
6834
                    }
6835

6836
                    yield $carry;
7✔
6837

6838
                    $carry = [];
7✔
6839
                    $i = 1;
7✔
6840
                }
6841

6842
                if ($carry !== []) {
7✔
6843
                    yield $carry;
7✔
6844
                }
6845
            };
7✔
6846
        } else {
6847
            $generator = function () use ($numberOfPieces) {
7✔
6848
                $carry = [];
7✔
6849
                $i = 1;
7✔
6850
                foreach ($this->getGenerator() as $value) {
7✔
6851
                    $carry[] = $value;
7✔
6852

6853
                    if ($i % $numberOfPieces !== 0) {
7✔
6854
                        ++$i;
7✔
6855

6856
                        continue;
7✔
6857
                    }
6858

6859
                    yield $carry;
7✔
6860

6861
                    $carry = [];
7✔
6862
                    $i = 1;
7✔
6863
                }
6864

6865
                if ($carry !== []) {
7✔
6866
                    yield $carry;
7✔
6867
                }
6868
            };
7✔
6869
        }
6870

6871
        return static::create(
7✔
6872
            $generator,
7✔
6873
            $this->iteratorClass,
7✔
6874
            false
7✔
6875
        );
7✔
6876
    }
6877

6878
    /**
6879
     * Strip all empty items from the current array.
6880
     *
6881
     * EXAMPLE: <code>
6882
     * a(['a' => 1, 'b' => ''])->stripEmpty(); // Arrayy[['a' => 1]]
6883
     * </code>
6884
     *
6885
     * @return static
6886
     *                <p>(Immutable)</p>
6887
     *
6888
     * @phpstan-return static
6889
     * @psalm-mutation-free
6890
     */
6891
    public function stripEmpty(): self
6892
    {
6893
        $generator = function () {
7✔
6894
            foreach ($this->getGenerator() as $key => $item) {
7✔
6895
                if ($item === null) {
7✔
6896
                    continue;
7✔
6897
                }
6898

6899
                if ((bool) \trim((string) $item)) {
7✔
6900
                    yield $key => $item;
7✔
6901
                }
6902
            }
6903
        };
7✔
6904

6905
        return static::create(
7✔
6906
            $generator(),
7✔
6907
            $this->iteratorClass,
7✔
6908
            false
7✔
6909
        );
7✔
6910
    }
6911

6912
    /**
6913
     * Swap two values between positions by key.
6914
     *
6915
     * EXAMPLE: <code>
6916
     * a(['a' => 1, 'b' => ''])->swap('a', 'b'); // Arrayy[['a' => '', 'b' => 1]]
6917
     * </code>
6918
     *
6919
     * @param int|string $swapA <p>a key in the array</p>
6920
     * @param int|string $swapB <p>a key in the array</p>
6921
     *
6922
     * @return static
6923
     *                <p>(Immutable)</p>
6924
     *
6925
     * @phpstan-return static
6926
     * @psalm-mutation-free
6927
     */
6928
    public function swap($swapA, $swapB): self
6929
    {
6930
        $array = $this->toArray();
7✔
6931

6932
        list($array[$swapA], $array[$swapB]) = [$array[$swapB], $array[$swapA]];
7✔
6933

6934
        return static::create(
7✔
6935
            $array,
7✔
6936
            $this->iteratorClass,
7✔
6937
            false
7✔
6938
        );
7✔
6939
    }
6940

6941
    /**
6942
     * Get the current array from the "Arrayy"-object.
6943
     * alias for "getArray()"
6944
     *
6945
     * @param bool $convertAllArrayyElements <p>
6946
     *                                       Convert all Child-"Arrayy" objects also to arrays.
6947
     *                                       </p>
6948
     * @param bool $preserveKeys             <p>
6949
     *                                       e.g.: A generator maybe return the same key more than once,
6950
     *                                       so maybe you will ignore the keys.
6951
     *                                       </p>
6952
     *
6953
     * @return array
6954
     *
6955
     * @phpstan-return ($preserveKeys is true ? array<TKey,T> : T[])
6956
     * @psalm-mutation-free
6957
     */
6958
    public function toArray(
6959
        bool $convertAllArrayyElements = false,
6960
        bool $preserveKeys = true
6961
    ): array {
6962
        if ($convertAllArrayyElements) {
6,776✔
6963
            // init
6964
            $array = [];
21✔
6965

6966
            foreach ($this->getGenerator() as $key => $value) {
21✔
6967
                if ($value instanceof self) {
21✔
6968
                    $value = $value->toArray(
14✔
6969
                        $convertAllArrayyElements,
14✔
6970
                        $preserveKeys
14✔
6971
                    );
14✔
6972
                }
6973

6974
                if ($preserveKeys) {
21✔
6975
                    $array[$key] = $value;
14✔
6976
                } else {
6977
                    $array[] = $value;
7✔
6978
                }
6979
            }
6980

6981
            /* @phpstan-ignore return.type */
6982
            return $array;
21✔
6983
        }
6984

6985
        return \iterator_to_array($this->getGenerator(), $preserveKeys);
6,769✔
6986
    }
6987

6988
    /**
6989
     * Get the current array from the "Arrayy"-object as list.
6990
     *
6991
     * @param bool $convertAllArrayyElements <p>
6992
     *                                       Convert all Child-"Arrayy" objects also to arrays.
6993
     *                                       </p>
6994
     *
6995
     * @return array
6996
     *
6997
     * @phpstan-return list<T>
6998
     * @psalm-mutation-free
6999
     */
7000
    public function toList(bool $convertAllArrayyElements = false): array
7001
    {
7002
        /** @var list<T> - currently phpstan can't return different types depending on the phpdocs params */
7003
        return $this->toArray(
7✔
7004
            $convertAllArrayyElements,
7✔
7005
            false
7✔
7006
        );
7✔
7007
    }
7008

7009
    /**
7010
     * Convert the current array to JSON.
7011
     *
7012
     * EXAMPLE: <code>
7013
     * a(['bar', ['foo']])->toJson(); // '["bar",{"1":"foo"}]'
7014
     * </code>
7015
     *
7016
     * @param int $options [optional] <p>e.g. JSON_PRETTY_PRINT</p>
7017
     * @param int $depth   [optional] <p>Set the maximum depth. Must be greater than zero.</p>
7018
     *
7019
     * @return string
7020
     */
7021
    public function toJson(int $options = 0, int $depth = 512): string
7022
    {
7023
        if ($depth < 1) {
91✔
7024
            $depth = 1;
×
7025
        }
7026

7027
        $return = \json_encode($this->toArray(), $options, $depth);
91✔
7028
        if ($return === false) {
91✔
7029
            return '';
×
7030
        }
7031

7032
        return $return;
91✔
7033
    }
7034

7035
    /**
7036
     * @param string[]|null $items  [optional]
7037
     * @param string[]      $helper [optional]
7038
     *
7039
     * @return static|static[]
7040
     *
7041
     * @phpstan-return static
7042
     */
7043
    public function toPermutation(?array $items = null, array $helper = []): self
7044
    {
7045
        // init
7046
        $return = [];
7✔
7047

7048
        if ($items === null) {
7✔
7049
            $items = $this->toArray();
7✔
7050
        }
7051

7052
        if (empty($items)) {
7✔
7053
            $return[] = $helper;
7✔
7054
        } else {
7055
            for ($i = \count($items) - 1; $i >= 0; --$i) {
7✔
7056
                $new_items = $items;
7✔
7057
                $new_helper = $helper;
7✔
7058
                list($tmp_helper) = \array_splice($new_items, $i, 1);
7✔
7059
                /** @noinspection PhpSillyAssignmentInspection */
7060
                /** @var string[] $new_items */
7061
                $new_items = $new_items;
7✔
7062
                \array_unshift($new_helper, $tmp_helper);
7✔
7063
                $return = \array_merge(
7✔
7064
                    $return,
7✔
7065
                    $this->toPermutation($new_items, $new_helper)->toArray()
7✔
7066
                );
7✔
7067
            }
7068
        }
7069

7070
        /** @var static $return  - help for phpstan */
7071
        $return = static::create(
7✔
7072
            $return,
7✔
7073
            $this->iteratorClass,
7✔
7074
            false
7✔
7075
        );
7✔
7076

7077
        return $return;
7✔
7078
    }
7079

7080
    /**
7081
     * Implodes array to a string with specified separator.
7082
     *
7083
     * @param string $separator [optional] <p>The element's separator.</p>
7084
     *
7085
     * @return string
7086
     *                <p>The string representation of array, separated by ",".</p>
7087
     */
7088
    public function toString(string $separator = ','): string
7089
    {
7090
        return $this->implode($separator);
133✔
7091
    }
7092

7093
    /**
7094
     * Return a duplicate free copy of the current array.
7095
     *
7096
     * EXAMPLE: <code>
7097
     * a([2 => 1, 3 => 2, 4 => 2])->uniqueNewIndex(); // Arrayy[1, 2]
7098
     * </code>
7099
     *
7100
     * @return $this
7101
     *               <p>(Mutable)</p>
7102
     *
7103
     * @phpstan-return static
7104
     */
7105
    public function uniqueNewIndex(): self
7106
    {
7107
        // INFO: \array_unique() can't handle e.g. "stdClass"-values in an array
7108

7109
        $this->array = $this->reduce(
91✔
7110
            static function ($resultArray, $value, $key) {
91✔
7111
                if (!\in_array($value, $resultArray, true)) {
84✔
7112
                    $resultArray[] = $value;
84✔
7113
                }
7114

7115
                return $resultArray;
84✔
7116
            },
91✔
7117
            []
91✔
7118
        )->toArray();
91✔
7119
        $this->generator = null;
91✔
7120

7121
        return $this;
91✔
7122
    }
7123

7124
    /**
7125
     * Return a duplicate free copy of the current array. (with the old keys)
7126
     *
7127
     * EXAMPLE: <code>
7128
     * a([2 => 1, 3 => 2, 4 => 2])->uniqueNewIndex(); // Arrayy[2 => 1, 3 => 2]
7129
     * </code>
7130
     *
7131
     * @return $this
7132
     *               <p>(Mutable)</p>
7133
     *
7134
     * @phpstan-return static
7135
     */
7136
    public function uniqueKeepIndex(): self
7137
    {
7138
        // INFO: \array_unique() can't handle e.g. "stdClass"-values in an array
7139

7140
        // init
7141
        $array = $this->toArray();
77✔
7142

7143
        /**
7144
         * @psalm-suppress MissingClosureReturnType
7145
         * @psalm-suppress MissingClosureParamType
7146
         */
7147
        $this->array = \array_reduce(
77✔
7148
            \array_keys($array),
77✔
7149
            static function ($resultArray, $key) use ($array) {
77✔
7150
                if (!\in_array($array[$key], $resultArray, true)) {
70✔
7151
                    $resultArray[$key] = $array[$key];
70✔
7152
                }
7153

7154
                return $resultArray;
70✔
7155
            },
77✔
7156
            []
77✔
7157
        );
77✔
7158
        $this->generator = null;
77✔
7159

7160
        return $this;
77✔
7161
    }
7162

7163
    /**
7164
     * alias: for "Arrayy->uniqueNewIndex()"
7165
     *
7166
     * @return static
7167
     *                <p>(Mutable) Return this Arrayy object, with the appended values.</p>
7168
     *
7169
     * @see          Arrayy::unique()
7170
     *
7171
     * @phpstan-return static
7172
     */
7173
    public function unique(): self
7174
    {
7175
        return $this->uniqueNewIndex();
91✔
7176
    }
7177

7178
    /**
7179
     * Prepends one or more values to the beginning of array at once.
7180
     *
7181
     * @param mixed ...$args
7182
     *
7183
     * @return $this
7184
     *               <p>(Mutable) Return this Arrayy object, with prepended elements to the beginning of array.</p>
7185
     *
7186
     * @phpstan-param  array<TKey,T> ...$args
7187
     * @phpstan-return static
7188
     */
7189
    public function unshift(...$args): self
7190
    {
7191
        $this->generatorToArray();
42✔
7192

7193
        if (
7194
            $this->checkPropertyTypes
42✔
7195
            &&
7196
            $this->properties !== []
42✔
7197
        ) {
7198
            foreach ($args as $key => $value) {
14✔
7199
                $this->checkType($key, $value);
14✔
7200
            }
7201
        }
7202

7203
        \array_unshift($this->array, ...$args); // @phpstan-ignore assign.propertyType
35✔
7204

7205
        return $this;
35✔
7206
    }
7207

7208
    /**
7209
     * Tests whether the given closure return something valid for all elements of this array.
7210
     *
7211
     * @param \Closure $closure the predicate
7212
     *
7213
     * @return bool
7214
     *              <p>TRUE, if the predicate yields TRUE for all elements, FALSE otherwise.</p>
7215
     *
7216
     * @phpstan-param \Closure(T,TKey):bool $closure
7217
     */
7218
    public function validate(\Closure $closure): bool
7219
    {
7220
        foreach ($this->getGenerator() as $key => $value) {
7✔
7221
            if (!$closure($value, $key)) {
7✔
7222
                return false;
7✔
7223
            }
7224
        }
7225

7226
        return true;
7✔
7227
    }
7228

7229
    /**
7230
     * Get all values from a array.
7231
     *
7232
     * EXAMPLE: <code>
7233
     * $arrayy = a([1 => 'foo', 2 => 'foo2', 3 => 'bar']);
7234
     * $arrayyTmp->values(); // Arrayy[0 => 'foo', 1 => 'foo2', 2 => 'bar']
7235
     * </code>
7236
     *
7237
     * @return static
7238
     *                <p>(Immutable)</p>
7239
     *
7240
     * @phpstan-return static
7241
     * @psalm-mutation-free
7242
     */
7243
    public function values(): self
7244
    {
7245
        return static::create(
14✔
7246
            function () {
14✔
7247
                foreach ($this->getGenerator() as $value) {
14✔
7248
                    yield $value;
14✔
7249
                }
7250
            },
14✔
7251
            $this->iteratorClass,
14✔
7252
            false
14✔
7253
        );
14✔
7254
    }
7255

7256
    /**
7257
     * Apply the given function to every element in the array, discarding the results.
7258
     *
7259
     * EXAMPLE: <code>
7260
     * $callable = function (&$value, $key) {
7261
     *     $value = $key;
7262
     * };
7263
     * $arrayy = a([1, 2, 3]);
7264
     * $arrayy->walk($callable); // Arrayy[0, 1, 2]
7265
     * </code>
7266
     *
7267
     * @param callable $callable
7268
     * @param bool     $recursive
7269
     *                            [optional] <p>Whether array will be walked recursively or no</p>
7270
     * @param mixed    $userData
7271
     *                            [optional] <p>
7272
     *                            If the optional $userData parameter is supplied,
7273
     *                            it will be passed as the third parameter to the $callable.
7274
     *                            </p>
7275
     *
7276
     * @return $this
7277
     *               <p>(Mutable) Return this Arrayy object, with modified elements.</p>
7278
     *
7279
     * @template TExtra
7280
     *              <p>The extra input value type.</p>
7281
     *
7282
     * @phostan-param TExtra $userData
7283
     * @phpstan-param  callable(T,TKey,?TExtra):void $callable
7284
     * @phpstan-return static
7285
     */
7286
    public function walk(
7287
        $callable,
7288
        bool $recursive = false,
7289
        $userData = self::ARRAYY_HELPER_WALK
7290
    ): self {
7291
        $this->generatorToArray();
84✔
7292

7293
        if ($this->array !== []) {
84✔
7294
            if ($recursive === true) {
70✔
7295
                if ($userData !== self::ARRAYY_HELPER_WALK) {
35✔
7296
                    \array_walk_recursive($this->array, $callable, $userData);
×
7297
                } else {
7298
                    \array_walk_recursive($this->array, $callable);
35✔
7299
                }
7300
            } else {
7301
                if ($userData !== self::ARRAYY_HELPER_WALK) {
35✔
7302
                    /* @phpstan-ignore argument.type (internal keys are array-key|TKey, the callback contract is TKey) */
UNCOV
7303
                    \array_walk($this->array, $callable, $userData);
×
7304
                } else {
7305
                    /* @phpstan-ignore argument.type */
7306
                    \array_walk($this->array, $callable);
35✔
7307
                }
7308
            }
7309
        }
7310

7311
        return $this;
84✔
7312
    }
7313

7314
    /**
7315
     * Returns a collection of matching items.
7316
     *
7317
     * @param string $keyOrPropertyOrMethod
7318
     *                                      <p>The property or method to evaluate.</p>
7319
     * @param mixed  $value
7320
     *                                      <p>The value to match.</p>
7321
     *
7322
     * @throws \InvalidArgumentException if property or method is not defined
7323
     *
7324
     * @return static
7325
     *
7326
     * @phpstan-return static
7327
     */
7328
    public function where(string $keyOrPropertyOrMethod, $value): self
7329
    {
7330
        return $this->filter(
35✔
7331
            function ($item) use ($keyOrPropertyOrMethod, $value) {
35✔
7332
                $accessorValue = $this->extractValue(
28✔
7333
                    $item,
28✔
7334
                    $keyOrPropertyOrMethod
28✔
7335
                );
28✔
7336

7337
                return $accessorValue === $value;
14✔
7338
            }
35✔
7339
        );
35✔
7340
    }
7341

7342
    /**
7343
     * Convert an array into an object.
7344
     *
7345
     * @param array $array
7346
     *
7347
     * @return \stdClass
7348
     *
7349
     * @phpstan-param array<int|string,mixed> $array
7350
     */
7351
    final protected static function arrayToObject(array $array = []): \stdClass
7352
    {
7353
        // init
7354
        $object = new \stdClass();
28✔
7355

7356
        if (\count($array, \COUNT_NORMAL) <= 0) {
28✔
7357
            return $object;
7✔
7358
        }
7359

7360
        foreach ($array as $name => $value) {
21✔
7361
            if (\is_array($value)) {
21✔
7362
                $object->{$name} = static::arrayToObject($value);
7✔
7363
            } else {
7364
                $object->{$name} = $value;
21✔
7365
            }
7366
        }
7367

7368
        return $object;
21✔
7369
    }
7370

7371
    /**
7372
     * @param array|\Generator|null $input         <p>
7373
     *                                             An array containing keys to return.
7374
     *                                             </p>
7375
     * @param mixed|null            $search_values [optional] <p>
7376
     *                                             If specified, then only keys containing these values are returned.
7377
     *                                             </p>
7378
     * @param bool                  $strict        [optional] <p>
7379
     *                                             Determines if strict comparison (===) should be used during the
7380
     *                                             search.
7381
     *                                             </p>
7382
     *
7383
     * @return array
7384
     *               <p>An array of all the keys in input.</p>
7385
     *
7386
     * @template TInput
7387
     *
7388
     * @phpstan-param  array<array-key,TInput>|\Generator<array-key,TInput>|null $input
7389
     * @phpstan-param T|T[]|null $search_values
7390
     * @phpstan-return array<int, TKey>
7391
     *
7392
     * @psalm-mutation-free
7393
     */
7394
    protected function array_keys_recursive(
7395
        $input = null,
7396
        $search_values = null,
7397
        bool $strict = true
7398
    ): array {
7399
        // init
7400
        $keys = [];
77✔
7401
        $keysTmp = [];
77✔
7402

7403
        if ($input === null) {
77✔
7404
            $input = $this->getGenerator();
28✔
7405
        }
7406

7407
        if ($search_values === null) {
77✔
7408
            foreach ($input as $key => $value) {
77✔
7409
                $keys[] = $key;
77✔
7410

7411
                // check if recursive is needed
7412
                if (\is_array($value)) {
77✔
7413
                    $keysTmp[] = $this->array_keys_recursive($value);
28✔
7414
                }
7415
            }
7416
        } else {
7417
            $is_array_tmp = \is_array($search_values);
7✔
7418

7419
            foreach ($input as $key => $value) {
7✔
7420
                if (
7421
                    (
7422
                        $is_array_tmp === false
7✔
7423
                        &&
7✔
7424
                        $strict === true
7✔
7425
                        &&
7✔
7426
                        $search_values === $value
7✔
7427
                    )
7428
                    ||
7429
                    (
7430
                        $is_array_tmp === false
7✔
7431
                        &&
7✔
7432
                        $strict === false
7✔
7433
                        &&
7✔
7434
                        $search_values == $value
7✔
7435
                    )
7436
                    ||
7437
                    (
7438
                        $is_array_tmp === true
7✔
7439
                        &&
7✔
7440
                        \in_array($value, $search_values, $strict)
7✔
7441
                    )
7442
                ) {
7443
                    $keys[] = $key;
7✔
7444
                }
7445

7446
                // check if recursive is needed
7447
                if (\is_array($value)) {
7✔
7448
                    $keysTmp[] = $this->array_keys_recursive($value);
7✔
7449
                }
7450
            }
7451
        }
7452

7453
        return $keysTmp === [] ? $keys : \array_merge($keys, ...$keysTmp);
77✔
7454
    }
7455

7456
    /**
7457
     * @param string     $path
7458
     * @param callable   $callable
7459
     * @param array|null $currentOffset
7460
     *
7461
     * @return void
7462
     *
7463
     * @phpstan-param array<array-key,mixed>|null $currentOffset
7464
     * @psalm-mutation-free
7465
     */
7466
    protected function callAtPath($path, $callable, &$currentOffset = null)
7467
    {
7468
        $this->generatorToArray();
70✔
7469

7470
        if ($currentOffset === null) {
70✔
7471
            $currentOffset = &$this->array;
70✔
7472
        }
7473

7474
        $explodedPath = \explode($this->pathSeparator, $path);
70✔
7475
        /* @phpstan-ignore identical.alwaysFalse */
7476
        if ($explodedPath === false) {
70✔
7477
            return;
×
7478
        }
7479

7480
        $nextPath = \array_shift($explodedPath);
70✔
7481
        if (!isset($currentOffset[$nextPath])) {
70✔
7482
            return;
7✔
7483
        }
7484

7485
        if ($explodedPath !== []) {
63✔
7486
            $this->callAtPath(
7✔
7487
                \implode($this->pathSeparator, $explodedPath),
7✔
7488
                $callable,
7✔
7489
                $currentOffset[$nextPath]
7✔
7490
            );
7✔
7491
        } else {
7492
            $callable($currentOffset[$nextPath]);
63✔
7493
        }
7494
    }
7495

7496
    /**
7497
     * Extracts the value of the given property or method from the object.
7498
     *
7499
     * @param mixed $object
7500
     *                                         <p>The Arrayy instance, object, or other value from which to extract the property or method value.</p>
7501
     * @param string    $keyOrPropertyOrMethod
7502
     *                                         <p>The property or method for which the
7503
     *                                         value should be extracted.</p>
7504
     *
7505
     * @throws \InvalidArgumentException if the method or property is not defined
7506
     *
7507
     * @return mixed
7508
     *               <p>The value extracted from the specified property or method.</p>
7509
     *
7510
     */
7511
    final protected function extractValue($object, string $keyOrPropertyOrMethod)
7512
    {
7513
        if ($object instanceof self && isset($object[$keyOrPropertyOrMethod])) {
35✔
7514
            $return = $object->get($keyOrPropertyOrMethod);
7✔
7515

7516
            if ($return instanceof self) {
7✔
7517
                return $return->toArray();
×
7518
            }
7519

7520
            return $return;
7✔
7521
        }
7522

7523
        // only use properties / methods that are accessible (and initialized) from here
7524
        if (\is_object($object) && \array_key_exists($keyOrPropertyOrMethod, \get_object_vars($object))) {
28✔
7525
            return $object->{$keyOrPropertyOrMethod};
7✔
7526
        }
7527

7528
        if (\is_object($object) && \is_callable([$object, $keyOrPropertyOrMethod])) {
21✔
7529
            return $object->{$keyOrPropertyOrMethod}();
7✔
7530
        }
7531

7532
        throw new \InvalidArgumentException(\sprintf('array-key & property & method "%s" not defined in %s', $keyOrPropertyOrMethod, \gettype($object)));
14✔
7533
    }
7534

7535
    /**
7536
     * create a fallback for array
7537
     *
7538
     * 1. use the current array, if it's a array
7539
     * 2. fallback to empty array, if there is nothing
7540
     * 3. call "getArray()" on object, if there is a "Arrayy"-object
7541
     * 4. call "createFromObject()" on object, if there is a "\Traversable"-object
7542
     * 5. call "__toArray()" on object, if the method exists
7543
     * 6. cast a string or object with "__toString()" into an array
7544
     * 7. throw a "InvalidArgumentException"-Exception
7545
     *
7546
     * @param mixed $data
7547
     *
7548
     * @throws \InvalidArgumentException
7549
     *
7550
     * @return array
7551
     *
7552
     * @phpstan-return array<mixed>|array<TKey,T>
7553
     */
7554
    protected function fallbackForArray(&$data): array
7555
    {
7556
        $data = $this->internalGetArray($data);
9,175✔
7557

7558
        if ($data === null) {
9,175✔
7559
            throw new \InvalidArgumentException('Passed value should be a array');
14✔
7560
        }
7561

7562
        return $data;
9,161✔
7563
    }
7564

7565
    /**
7566
     * @param bool $preserveKeys <p>
7567
     *                           e.g.: A generator maybe return the same key more than once,
7568
     *                           so maybe you will ignore the keys.
7569
     *                           </p>
7570
     *
7571
     * @return bool
7572
     *
7573
     * @noinspection ReturnTypeCanBeDeclaredInspection
7574
     * @psalm-mutation-free :/
7575
     */
7576
    protected function generatorToArray(bool $preserveKeys = true)
7577
    {
7578
        if ($this->generator) {
8,475✔
7579
            $this->array = $this->toArray(false, $preserveKeys);
21✔
7580
            $this->generator = null;
21✔
7581

7582
            return true;
21✔
7583
        }
7584

7585
        return false;
8,475✔
7586
    }
7587

7588
    /**
7589
     * Get correct PHP constant for direction.
7590
     *
7591
     * @param int|string $direction
7592
     *
7593
     * @return int
7594
     * @psalm-mutation-free
7595
     */
7596
    protected function getDirection($direction): int
7597
    {
7598
        if ((string) $direction === $direction) {
301✔
7599
            $direction = \strtolower($direction);
70✔
7600

7601
            if ($direction === 'desc') {
70✔
7602
                $direction = \SORT_DESC;
14✔
7603
            } else {
7604
                $direction = \SORT_ASC;
63✔
7605
            }
7606
        }
7607

7608
        if (
7609
            $direction !== \SORT_DESC
301✔
7610
            &&
7611
            $direction !== \SORT_ASC
301✔
7612
        ) {
7613
            $direction = \SORT_ASC;
×
7614
        }
7615

7616
        return $direction;
301✔
7617
    }
7618

7619
    /**
7620
     * @return TypeCheckInterface[]
7621
     *
7622
     * @noinspection ReturnTypeCanBeDeclaredInspection
7623
     */
7624
    protected function getPropertiesFromPhpDoc()
7625
    {
7626
        static $PROPERTY_CACHE = [];
523✔
7627
        static $OPTIONAL_PROPERTY_CACHE = [];
523✔
7628
        $cacheKey = 'Class::' . static::class;
523✔
7629

7630
        if (isset($PROPERTY_CACHE[$cacheKey])) {
523✔
7631
            $this->optionalProperties = $OPTIONAL_PROPERTY_CACHE[$cacheKey] ?? [];
467✔
7632

7633
            return $PROPERTY_CACHE[$cacheKey];
467✔
7634
        }
7635

7636
        $properties = $this->getPropertiesFromNativeDefinitions();
145✔
7637
        $optionalProperties = [];
145✔
7638
        $phpDocPropertyAnnotationStyle = null;
145✔
7639

7640
        $reflector = new \ReflectionClass($this);
145✔
7641
        $factory = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
145✔
7642
        $docComment = $reflector->getDocComment();
145✔
7643
        if ($docComment) {
145✔
7644
            $docblock = $factory->create($docComment);
138✔
7645
            $this->addPropertiesFromDocBlock($docblock, $properties, $optionalProperties, $phpDocPropertyAnnotationStyle);
138✔
7646
        }
7647

7648
        /** @noinspection PhpAssignmentInConditionInspection */
7649
        while ($reflector = $reflector->getParentClass()) {
138✔
7650
            $docComment = $reflector->getDocComment();
138✔
7651
            if ($docComment) {
138✔
7652
                $docblock = $factory->create($docComment);
138✔
7653
                $this->addPropertiesFromDocBlock($docblock, $properties, $optionalProperties, $phpDocPropertyAnnotationStyle);
138✔
7654
            }
7655
        }
7656

7657
        $this->optionalProperties = $optionalProperties;
131✔
7658
        $OPTIONAL_PROPERTY_CACHE[$cacheKey] = $optionalProperties;
131✔
7659

7660
        return $PROPERTY_CACHE[$cacheKey] = $properties;
131✔
7661
    }
7662

7663
    /**
7664
     * Merge property definitions from a docblock into the collected property map.
7665
     *
7666
     * @param \phpDocumentor\Reflection\DocBlock $docblock
7667
     * @param TypeCheckInterface[]               $properties
7668
     * @param array<string, true>                $optionalProperties
7669
     * @param 'array-shape'|'property'|null      $phpDocPropertyAnnotationStyle
7670
     *
7671
     * @return void
7672
     */
7673
    private function addPropertiesFromDocBlock($docblock, array &$properties, array &$optionalProperties, ?string &$phpDocPropertyAnnotationStyle): void
7674
    {
7675
        $propertyTags = $docblock->getTagsByName('property');
145✔
7676
        $arrayShapeItems = $this->getArrayShapeItemsFromDocBlock($docblock);
145✔
7677

7678
        if ($propertyTags !== [] && $arrayShapeItems !== []) {
145✔
7679
            throw new \TypeError('Use either @property tags or array-shape annotations for Arrayy property definitions, not both.');
7✔
7680
        }
7681

7682
        $currentPhpDocPropertyAnnotationStyle = null;
138✔
7683
        if ($propertyTags !== []) {
138✔
7684
            $currentPhpDocPropertyAnnotationStyle = 'property';
49✔
7685
        } elseif ($arrayShapeItems !== []) {
138✔
7686
            $currentPhpDocPropertyAnnotationStyle = 'array-shape';
56✔
7687
        }
7688

7689
        if (
7690
            $currentPhpDocPropertyAnnotationStyle !== null
138✔
7691
            &&
7692
            $phpDocPropertyAnnotationStyle !== null
138✔
7693
            &&
7694
            $phpDocPropertyAnnotationStyle !== $currentPhpDocPropertyAnnotationStyle
138✔
7695
        ) {
7696
            throw new \TypeError('Use either @property tags or array-shape annotations for Arrayy property definitions, not both.');
7✔
7697
        }
7698

7699
        if ($currentPhpDocPropertyAnnotationStyle !== null) {
138✔
7700
            $phpDocPropertyAnnotationStyle = $currentPhpDocPropertyAnnotationStyle;
98✔
7701
        }
7702

7703
        /** @var \phpDocumentor\Reflection\DocBlock\Tags\Property $tag */
7704
        foreach ($propertyTags as $tag) {
138✔
7705
            $typeName = $tag->getVariableName();
42✔
7706
            /** @var string|null $typeName */
7707
            if (
7708
                $typeName !== null
42✔
7709
                &&
7710
                isset($properties[$typeName]) === false
42✔
7711
            ) {
7712
                $typeCheckPhpDoc = TypeCheckPhpDoc::fromPhpDocumentorProperty($tag, $typeName);
42✔
7713
                if ($typeCheckPhpDoc !== null) {
42✔
7714
                    $properties[$typeName] = $typeCheckPhpDoc;
42✔
7715
                    unset($optionalProperties[$typeName]);
42✔
7716
                }
7717
            }
7718
        }
7719

7720
        foreach ($arrayShapeItems as $item) {
138✔
7721
            $typeName = (string) $item->getKey();
56✔
7722
            if ($typeName === '') {
56✔
7723
                continue;
×
7724
            }
7725

7726
            $typeName = \trim($typeName, '\'"');
56✔
7727
            if (isset($properties[$typeName])) {
56✔
7728
                continue;
×
7729
            }
7730

7731
            $typeCheckPhpDoc = TypeCheckPhpDoc::fromDocTypeObject($typeName, $item->getValue());
56✔
7732
            $properties[$typeName] = $typeCheckPhpDoc;
56✔
7733
            if ($item->isOptional()) {
56✔
7734
                $optionalProperties[$typeName] = true;
35✔
7735
            }
7736
        }
7737
    }
7738

7739
    /**
7740
     * Extract array-shape items from supported @template and @extends annotations.
7741
     *
7742
     * @param \phpDocumentor\Reflection\DocBlock $docblock
7743
     *
7744
     * @return \phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem[]
7745
     */
7746
    private function getArrayShapeItemsFromDocBlock($docblock): array
7747
    {
7748
        if (!\class_exists('\phpDocumentor\Reflection\PseudoTypes\ArrayShape')) {
145✔
7749
            return [];
×
7750
        }
7751

7752
        $items = [];
145✔
7753
        foreach ($docblock->getTagsByName('template') as $tag) {
145✔
7754
            if (
7755
                $tag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Template
145✔
7756
                &&
7757
                $tag->getTemplateName() === 'T'
145✔
7758
                &&
7759
                $tag->getBound() instanceof \phpDocumentor\Reflection\PseudoTypes\ArrayShape
145✔
7760
            ) {
7761
                foreach ($tag->getBound()->getItems() as $item) {
49✔
7762
                    $items[] = $item;
49✔
7763
                }
7764
            }
7765
        }
7766

7767
        foreach ($docblock->getTagsByName('extends') as $tag) {
145✔
7768
            if (!$tag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Extends_) {
145✔
7769
                continue;
×
7770
            }
7771

7772
            $type = $tag->getType();
145✔
7773
            if (
7774
                !$type instanceof \phpDocumentor\Reflection\PseudoTypes\Generic
145✔
7775
                ||
7776
                !$this->isArrayyGenericTarget((string) $type->getFqsen())
145✔
7777
            ) {
7778
                continue;
131✔
7779
            }
7780

7781
            foreach ($type->getTypes() as $genericType) {
138✔
7782
                if ($genericType instanceof \phpDocumentor\Reflection\PseudoTypes\ArrayShape) {
138✔
7783
                    foreach ($genericType->getItems() as $item) {
14✔
7784
                        $items[] = $item;
14✔
7785
                    }
7786
                }
7787
            }
7788
        }
7789

7790
        return $items;
145✔
7791
    }
7792

7793
    /**
7794
     * Check whether a generic annotation target is Arrayy, ArrayyStrict, or an Arrayy subclass.
7795
     *
7796
     * @param string $fqcn
7797
     *
7798
     * @return bool
7799
     */
7800
    private function isArrayyGenericTarget(string $fqcn): bool
7801
    {
7802
        $fqcn = \ltrim($fqcn, '\\');
145✔
7803
        if ($fqcn === '') {
145✔
7804
            return false;
×
7805
        }
7806

7807
        if (\in_array($fqcn, [self::class, ArrayyStrict::class], true)) {
145✔
7808
            return true;
138✔
7809
        }
7810

7811
        return \class_exists($fqcn) && \is_a($fqcn, self::class, true);
131✔
7812
    }
7813

7814
    /**
7815
     * @return TypeCheckInterface[]
7816
     */
7817
    protected function getPropertiesFromNativeDefinitions(): array
7818
    {
7819
        $properties = [];
145✔
7820
        $reflector = new \ReflectionClass($this);
145✔
7821
        $reservedProperties = self::getReservedPropertyNames();
145✔
7822

7823
        do {
7824
            if ($reflector->getName() === self::class) {
145✔
7825
                break;
145✔
7826
            }
7827

7828
            foreach ($reflector->getProperties() as $property) {
145✔
7829
                if (
7830
                    $property->getDeclaringClass()->getName() !== $reflector->getName()
145✔
7831
                    ||
7832
                    $property->isStatic()
124✔
7833
                    ||
7834
                    isset($reservedProperties[$property->getName()])
124✔
7835
                    ||
7836
                    isset($properties[$property->getName()])
145✔
7837
                ) {
7838
                    continue;
145✔
7839
                }
7840

7841
                $properties[$property->getName()] = TypeCheckPhpDoc::fromReflectionProperty($property);
19✔
7842
            }
7843
        } while ($reflector = $reflector->getParentClass());
145✔
7844

7845
        return $properties;
145✔
7846
    }
7847

7848
    /**
7849
     * @return array<string, true>
7850
     */
7851
    private static function getReservedPropertyNames(): array
7852
    {
7853
        static $reservedProperties = null;
145✔
7854

7855
        if ($reservedProperties !== null) {
145✔
7856
            return $reservedProperties;
138✔
7857
        }
7858

7859
        $reservedProperties = [];
7✔
7860
        $reflector = new \ReflectionClass(self::class);
7✔
7861
        foreach ($reflector->getProperties() as $property) {
7✔
7862
            if ($property->getDeclaringClass()->getName() !== self::class) {
7✔
7863
                continue;
×
7864
            }
7865

7866
            $reservedProperties[$property->getName()] = true;
7✔
7867
        }
7868

7869
        return $reservedProperties;
7✔
7870
    }
7871

7872
    /**
7873
     * @param string $glue
7874
     * @param mixed  $pieces
7875
     * @param bool   $useKeys
7876
     *
7877
     * @return string
7878
     *
7879
     * @phpstan-param scalar|object|self<TKey|T>|array<TKey,T>|array<T> $pieces
7880
     * @psalm-mutation-free
7881
     */
7882
    protected function implode_recursive(
7883
        $glue = '',
7884
        $pieces = [],
7885
        bool $useKeys = false
7886
    ): string {
7887
        if ($pieces instanceof self) {
259✔
7888
            $pieces = $pieces->toArray();
7✔
7889
        }
7890

7891
        if (\is_array($pieces)) {
259✔
7892
            /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
7893
            /** @phpstan-var array<TKey,T> $pieces */
7894
            $pieces = $pieces;
259✔
7895

7896
            $pieces_count = \count($pieces, \COUNT_NORMAL);
259✔
7897
            $pieces_count_not_zero = $pieces_count > 0;
259✔
7898

7899
            return \implode(
259✔
7900
                $glue,
259✔
7901
                \array_map(
259✔
7902
                    [$this, 'implode_recursive'],
259✔
7903
                    \array_fill(0, ($pieces_count_not_zero ? $pieces_count : 1), $glue),
259✔
7904
                    ($useKeys === true && $pieces_count_not_zero ? $this->array_keys_recursive($pieces) : $pieces)
259✔
7905
                )
259✔
7906
            );
259✔
7907
        }
7908

7909
        if (
7910
            \is_scalar($pieces) === true
259✔
7911
            ||
7912
            $pieces instanceof \Stringable
259✔
7913
        ) {
7914
            return (string) $pieces;
231✔
7915
        }
7916

7917
        return '';
56✔
7918
    }
7919

7920
    /**
7921
     * @param mixed                 $needle   <p>
7922
     *                                        The searched value.
7923
     *                                        </p>
7924
     *                                        <p>
7925
     *                                        If needle is a string, the comparison is done
7926
     *                                        in a case-sensitive manner.
7927
     *                                        </p>
7928
     * @param array|\Generator|null $haystack <p>
7929
     *                                        The array.
7930
     *                                        </p>
7931
     * @param bool                  $strict   [optional] <p>
7932
     *                                        If the third parameter strict is set to true
7933
     *                                        then the in_array function will also check the
7934
     *                                        types of the
7935
     *                                        needle in the haystack.
7936
     *                                        </p>
7937
     *
7938
     * @return bool
7939
     *              <p>true if needle is found in the array, false otherwise</p>
7940
     *
7941
     * @phpstan-param array<array-key, mixed>|array<TKey,T>|\Generator<TKey,T>|null $haystack
7942
     *
7943
     * @psalm-mutation-free
7944
     */
7945
    protected function in_array_recursive($needle, $haystack = null, $strict = true): bool
7946
    {
7947
        if ($haystack === null) {
129✔
7948
            $haystack = $this->getGenerator();
×
7949
        }
7950

7951
        foreach ($haystack as $item) {
129✔
7952
            if (\is_array($item)) {
101✔
7953
                $returnTmp = $this->in_array_recursive($needle, $item, $strict);
24✔
7954
            } else {
7955
                /** @noinspection NestedPositiveIfStatementsInspection */
7956
                if ($strict === true) {
101✔
7957
                    $returnTmp = $item === $needle;
101✔
7958
                } else {
7959
                    $returnTmp = $item == $needle;
×
7960
                }
7961
            }
7962

7963
            if ($returnTmp === true) {
101✔
7964
                return true;
73✔
7965
            }
7966
        }
7967

7968
        return false;
56✔
7969
    }
7970

7971
    /**
7972
     * @param mixed $data
7973
     *
7974
     * @return array<mixed>|null
7975
     */
7976
    protected function internalGetArray(&$data)
7977
    {
7978
        if (\is_array($data)) {
9,175✔
7979
            return $data;
9,133✔
7980
        }
7981

7982
        if (!$data) {
819✔
7983
            return [];
49✔
7984
        }
7985

7986
        if (\is_object($data) === true) {
812✔
7987
            if ($data instanceof \ArrayObject) {
763✔
7988
                return $data->getArrayCopy();
35✔
7989
            }
7990

7991
            if ($data instanceof \Generator) {
735✔
7992
                return static::createFromGeneratorImmutable($data)->toArray();
7✔
7993
            }
7994

7995
            if ($data instanceof \Traversable) {
728✔
7996
                return static::createFromObject($data)->toArray();
×
7997
            }
7998

7999
            if ($data instanceof \JsonSerializable) {
728✔
8000
                return (array) $data->jsonSerialize();
×
8001
            }
8002

8003
            if (\method_exists($data, '__toArray')) {
728✔
8004
                return (array) $data->__toArray();
×
8005
            }
8006

8007
            if (\method_exists($data, '__toString')) {
728✔
8008
                return [(string) $data];
×
8009
            }
8010
        }
8011

8012
        if (\is_callable($data)) {
777✔
8013
            /**
8014
             * @psalm-suppress InvalidPropertyAssignmentValue - why?
8015
             */
8016
            $this->generator = new ArrayyRewindableGenerator($data);
714✔
8017

8018
            return [];
714✔
8019
        }
8020

8021
        if (\is_scalar($data)) {
77✔
8022
            return [$data];
63✔
8023
        }
8024

8025
        return null;
14✔
8026
    }
8027

8028
    /**
8029
     * Internal mechanics of remove method.
8030
     *
8031
     * @param float|int|string|null $key
8032
     *
8033
     * @return bool
8034
     */
8035
    protected function internalRemove($key): bool
8036
    {
8037
        $this->generatorToArray();
161✔
8038

8039
        if (
8040
            $this->pathSeparator
161✔
8041
            &&
8042
            (string) $key === $key
161✔
8043
            &&
8044
            \strpos($key, $this->pathSeparator) !== false
161✔
8045
        ) {
8046
            $path = \explode($this->pathSeparator, (string) $key);
×
8047
            // crawl though the keys
8048
            while (\count($path, \COUNT_NORMAL) > 1) {
×
8049
                $key = \array_shift($path);
×
8050

8051
                if (!$this->has($key)) {
×
8052
                    return false;
×
8053
                }
8054

8055
                $this->array = &$this->array[$key];
×
8056
            }
8057

8058
            $key = \array_shift($path);
×
8059
        }
8060

8061
        if ($key === null) {
161✔
8062
            return false;
7✔
8063
        }
8064

8065
        if (\is_float($key)) {
154✔
8066
            return false;
7✔
8067
        }
8068

8069
        unset($this->array[$key]);
147✔
8070

8071
        return true;
147✔
8072
    }
8073

8074
    /**
8075
     * Internal mechanic of set method.
8076
     *
8077
     * @param int|string|null $key
8078
     * @param mixed           $value
8079
     * @param bool            $checkProperties
8080
     *
8081
     * @return bool
8082
     *
8083
     * @phpstan-param array-key|null $key
8084
     * @phpstan-param T $value
8085
     */
8086
    protected function internalSet(
8087
        $key,
8088
        &$value,
8089
        bool $checkProperties = true
8090
    ): bool {
8091
        if (
8092
            $checkProperties === true
8,076✔
8093
            &&
8094
            $this->properties !== []
8,076✔
8095
        ) {
8096
            $this->checkType($key, $value);
1,190✔
8097
        }
8098

8099
        if ($key === null) {
8,034✔
8100
            return false;
×
8101
        }
8102

8103
        $this->generatorToArray();
8,034✔
8104

8105
        $array = &$this->array;
8,034✔
8106

8107
        /**
8108
         * https://github.com/vimeo/psalm/issues/2536
8109
         *
8110
         * @psalm-suppress PossiblyInvalidArgument
8111
         * @psalm-suppress InvalidScalarArgument
8112
         */
8113
        if (
8114
            $this->pathSeparator
8,034✔
8115
            &&
8116
            (string) $key === $key
8,034✔
8117
            &&
8118
            \strpos($key, $this->pathSeparator) !== false
8,034✔
8119
        ) {
8120
            $path = \explode($this->pathSeparator, (string) $key);
63✔
8121
            // crawl through the keys
8122
            while (\count($path, \COUNT_NORMAL) > 1) {
63✔
8123
                $key = \array_shift($path);
63✔
8124

8125
                $array = &$array[$key];
63✔
8126
            }
8127

8128
            $key = \array_shift($path);
63✔
8129
        }
8130

8131
        if ($array === null) {
8,034✔
8132
            $array = [];
28✔
8133
        } elseif (!\is_array($array)) {
8,013✔
8134
            throw new \RuntimeException('Can not set value at this path "' . $key . '" because (' . \gettype($array) . ')"' . \print_r($array, true) . '" is not an array.');
7✔
8135
        }
8136

8137
        $array[$key] = $value;
8,034✔
8138

8139
        return true;
8,034✔
8140
    }
8141

8142
    /**
8143
     * Convert a object into an array.
8144
     *
8145
     * @param mixed|object $object
8146
     *
8147
     * @return array|mixed
8148
     *
8149
     * @psalm-mutation-free
8150
     */
8151
    protected static function objectToArray($object)
8152
    {
8153
        if (!\is_object($object)) {
42✔
8154
            return $object;
35✔
8155
        }
8156

8157
        $object = \get_object_vars($object);
42✔
8158

8159
        /**
8160
         * @psalm-suppress PossiblyInvalidArgument - the parameter is always some kind of array - false-positive from psalm?
8161
         */
8162
        return \array_map([static::class, 'objectToArray'], $object);
42✔
8163
    }
8164

8165
    /**
8166
     * @param array $data
8167
     * @param bool  $checkPropertiesInConstructor
8168
     *
8169
     * @return void
8170
     *
8171
     * @phpstan-param array<mixed,T> $data
8172
     */
8173
    protected function setInitialValuesAndProperties(array &$data, bool $checkPropertiesInConstructor)
8174
    {
8175
        $checkPropertiesInConstructor = $this->checkForMissingPropertiesInConstructor === true
9,161✔
8176
                                        &&
9,161✔
8177
                                        $checkPropertiesInConstructor === true;
9,161✔
8178

8179
        if ($this->properties === []) {
9,161✔
8180
            if (
8181
                $this->checkPropertyTypes === true
8,468✔
8182
                ||
8183
                $checkPropertiesInConstructor === true
8,468✔
8184
            ) {
8185
                $this->properties = $this->getPropertiesFromPhpDoc();
495✔
8186
            }
8187

8188
            /** @var TypeCheckInterface[] $properties */
8189
            $properties = $this->properties;
8,454✔
8190
            $requiredProperties = \array_diff_key($properties, $this->optionalProperties);
8,454✔
8191

8192
            if (
8193
                $this->checkPropertiesMismatchInConstructor === true
8,454✔
8194
                &&
8195
                \count($data) !== 0
8,454✔
8196
                &&
8197
                \count(\array_diff_key($requiredProperties, $data)) > 0
8,454✔
8198
            ) {
8199
                throw new \TypeError('Property mismatch - input: ' . \print_r(\array_keys($data), true) . ' | expected: ' . \print_r(\array_keys($requiredProperties), true));
14✔
8200
            }
8201
        }
8202

8203
        foreach ($data as $key => &$valueInner) {
9,133✔
8204
            $this->internalSet(
7,950✔
8205
                $key,
7,950✔
8206
                $valueInner,
7,950✔
8207
                $checkPropertiesInConstructor
7,950✔
8208
            );
7,950✔
8209
        }
8210
    }
8211

8212
    /**
8213
     * sorting keys
8214
     *
8215
     * @param array      $elements
8216
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
8217
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
8218
     *                              <strong>SORT_NATURAL</strong></p>
8219
     *
8220
     * @return $this
8221
     *               <p>(Mutable) Return this Arrayy object.</p>
8222
     *
8223
     * @phpstan-param  array<mixed|TKey,T> $elements
8224
     * @phpstan-return static
8225
     */
8226
    protected function sorterKeys(
8227
        array &$elements,
8228
        $direction = \SORT_ASC,
8229
        int $strategy = \SORT_REGULAR
8230
    ): self {
8231
        $direction = $this->getDirection($direction);
126✔
8232

8233
        switch ($direction) {
8234
            case 'desc':
126✔
8235
            case \SORT_DESC:
8236
                \krsort($elements, $strategy);
42✔
8237

8238
                break;
42✔
8239
            case 'asc':
91✔
8240
            case \SORT_ASC:
91✔
8241
            default:
8242
                \ksort($elements, $strategy);
91✔
8243
        }
8244

8245
        return $this;
126✔
8246
    }
8247

8248
    /**
8249
     * @param array      $elements  <p>Warning: used as reference</p>
8250
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
8251
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
8252
     *                              <strong>SORT_NATURAL</strong></p>
8253
     * @param bool       $keepKeys
8254
     *
8255
     * @return $this
8256
     *               <p>(Mutable) Return this Arrayy object.</p>
8257
     *
8258
     * @phpstan-param array<mixed|TKey,T> $elements
8259
     * @phpstan-return static
8260
     */
8261
    protected function sorting(
8262
        array &$elements,
8263
        $direction = \SORT_ASC,
8264
        int $strategy = \SORT_REGULAR,
8265
        bool $keepKeys = false
8266
    ): self {
8267
        $direction = $this->getDirection($direction);
168✔
8268

8269
        if (!$strategy) {
168✔
8270
            $strategy = \SORT_REGULAR;
168✔
8271
        }
8272

8273
        switch ($direction) {
8274
            case 'desc':
168✔
8275
            case \SORT_DESC:
8276
                if ($keepKeys) {
91✔
8277
                    \arsort($elements, $strategy);
63✔
8278
                } else {
8279
                    \rsort($elements, $strategy);
28✔
8280
                }
8281

8282
                break;
91✔
8283
            case 'asc':
77✔
8284
            case \SORT_ASC:
77✔
8285
            default:
8286
                if ($keepKeys) {
77✔
8287
                    \asort($elements, $strategy);
28✔
8288
                } else {
8289
                    \sort($elements, $strategy);
49✔
8290
                }
8291
        }
8292

8293
        return $this;
168✔
8294
    }
8295

8296
    /**
8297
     * @param array $array
8298
     *
8299
     * @return array
8300
     *
8301
     * @phpstan-param array<array-key, mixed> $array
8302
     * @phpstan-return array<array-key, mixed>
8303
     *
8304
     * @psalm-mutation-free
8305
     */
8306
    private function getArrayRecursiveHelperArrayy(array $array)
8307
    {
8308
        if ($array === []) {
175✔
8309
            return [];
×
8310
        }
8311

8312
        \array_walk_recursive(
175✔
8313
            $array,
175✔
8314
            /**
8315
             * @param array|self $item
8316
             *
8317
             * @return void
8318
             */
8319
            static function (&$item) {
175✔
8320
                if ($item instanceof self) {
175✔
8321
                    $item = $item->getArray();
7✔
8322
                }
8323
            }
175✔
8324
        );
175✔
8325

8326
        return $array;
175✔
8327
    }
8328

8329
    /**
8330
     * @param int|string|null $key
8331
     * @param mixed           $value
8332
     *
8333
     * @return void
8334
     */
8335
    private function checkType($key, $value)
8336
    {
8337
        if (
8338
            $key !== null
1,190✔
8339
            &&
8340
            isset($this->properties[$key]) === false
1,190✔
8341
            &&
8342
            $this->checkPropertiesMismatch === true
1,190✔
8343
        ) {
8344
            throw new \TypeError('The key "' . $key . '" does not exist as a property definition. (' . \get_class($this) . ').');
28✔
8345
        }
8346

8347
        if (isset($this->properties[self::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES])) {
1,190✔
8348
            $this->properties[self::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES]->checkType($value);
833✔
8349
        } elseif ($key !== null && isset($this->properties[$key])) {
434✔
8350
            $this->properties[$key]->checkType($value);
434✔
8351
        }
8352
    }
8353
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc