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

voku / Arrayy / 24923799365

25 Apr 2026 05:39AM UTC coverage: 92.372%. Remained the same
24923799365

push

github

web-flow
Merge pull request #164 from voku/copilot/prepare-changelog-release-notes

Add upcoming release notes to CHANGELOG

2749 of 2976 relevant lines covered (92.37%)

249.91 hits per line

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

91.96
/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 class-string<\Arrayy\ArrayyIterator<TKey,T>> $iteratorClass
121
     */
122
    public function __construct(
123
        $data = [],
124
        string $iteratorClass = ArrayyIterator::class,
125
        bool $checkPropertiesInConstructor = true
126
    ) {
127
        $data = $this->fallbackForArray($data);
9,119 ✔
128

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

135
        $this->setInitialValuesAndProperties($data, $checkPropertiesInConstructor);
9,105 ✔
136

137
        $this->setIteratorClass($iteratorClass);
8,916 ✔
138
    }
139

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

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

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

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

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

175
        return $return;
×
176
    }
177

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

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

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

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

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

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

255
        return $return;
1,167 ✔
256
    }
257

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

284
            $this->internalSet($key, $value);
35 ✔
285

286
            return $this;
28 ✔
287
        }
288

289
        return $this->append($value);
63 ✔
290
    }
291

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

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

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

332
        return $this;
140 ✔
333
    }
334

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

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

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

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

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

400
        \asort($this->array, $sort_flags);
28 ✔
401

402
        return $this;
28 ✔
403
    }
404

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

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

429
        return $that;
28 ✔
430
    }
431

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

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

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

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

513
        $this->array = $array;
7 ✔
514
        $this->generator = null;
7 ✔
515

516
        return $this->array;
7 ✔
517
    }
518

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

530
        return $this->array;
42 ✔
531
    }
532

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

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

558
            $this->generator = $generatorTmp;
14 ✔
559

560
            return $this->generator;
14 ✔
561
        }
562

563
        $iterator = $this->getIteratorClass();
238 ✔
564

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

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

572
        return $return;
×
573
    }
574

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

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

606
        \ksort($this->array, $sort_flags);
28 ✔
607

608
        return $this;
28 ✔
609
    }
610

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

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

634
        return $that;
28 ✔
635
    }
636

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

650
        \natcasesort($this->array);
56 ✔
651

652
        return $this;
56 ✔
653
    }
654

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

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

673
        return $that;
28 ✔
674
    }
675

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

689
        \natsort($this->array);
70 ✔
690

691
        return $this;
70 ✔
692
    }
693

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

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

712
        return $that;
28 ✔
713
    }
714

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

733
        $offsetExists = $this->keyExists($offset);
1,377 ✔
734
        if ($offsetExists === true) {
1,377 ✔
735
            return true;
1,230 ✔
736
        }
737

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

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

768
        return $offsetExists;
973 ✔
769
    }
770

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

789
        if ($this->offsetExists($offset)) {
1,160 ✔
790
            $value = &$this->__get($offset);
1,146 ✔
791
        }
792

793
        return $value;
1,160 ✔
794
    }
795

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

809
        if ($offset === null) {
294 ✔
810
            if ($this->properties !== []) {
63 ✔
811
                $this->checkType(null, $value);
28 ✔
812
            }
813

814
            $this->array[] = $value;
56 ✔
815
        } else {
816
            $this->internalSet(
238 ✔
817
                $offset,
238 ✔
818
                $value,
238 ✔
819
                true
238 ✔
820
            );
238 ✔
821
        }
822
    }
823

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

837
        if ($this->array === []) {
182 ✔
838
            return;
42 ✔
839
        }
840

841
        if ($this->keyExists($offset)) {
147 ✔
842
            unset($this->array[$offset]);
98 ✔
843

844
            return;
98 ✔
845
        }
846

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

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

879
        unset($this->array[$offset]);
70 ✔
880
    }
881

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

895
        return \serialize($this);
7 ✔
896
    }
897

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

915
            return;
8,916 ✔
916
        }
917

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

927
                return;
×
928
            }
929
        }
930

931
        throw new \InvalidArgumentException('The iterator class does not exist: ' . $iteratorClass);
×
932
    }
933

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

952
        \uasort($this->array, $callable);
56 ✔
953

954
        return $this;
56 ✔
955
    }
956

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

975
        /**
976
         * @psalm-suppress ImpureMethodCall - object is already cloned
977
         */
978
        $that->uasort($callable);
28 ✔
979

980
        return $that;
28 ✔
981
    }
982

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

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

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

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

1062
        if ($key !== null) {
7 ✔
1063
            if (
1064
                isset($this->array[$key])
7 ✔
1065
                &&
1066
                \is_array($this->array[$key])
7 ✔
1067
            ) {
1068
                foreach ($values as $value) {
7 ✔
1069
                    $this->array[$key][] = $value;
7 ✔
1070
                }
1071
            } else {
1072
                foreach ($values as $value) {
3 ✔
1073
                    $this->array[$key] = $value;
×
1074
                }
1075
            }
1076
        } else {
1077
            foreach ($values as $value) {
×
1078
                $this->array[] = $value;
×
1079
            }
1080
        }
1081

1082
        return $this;
7 ✔
1083
    }
1084

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

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

1113
        return self::create(
70 ✔
1114
            $result,
70 ✔
1115
            $this->iteratorClass,
70 ✔
1116
            false
70 ✔
1117
        );
70 ✔
1118
    }
1119

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

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

1148
        return self::create($result, $this->iteratorClass, false);
70 ✔
1149
    }
1150

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

1163
        \arsort($this->array);
28 ✔
1164

1165
        return $this;
28 ✔
1166
    }
1167

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

1181
        $that->generatorToArray();
70 ✔
1182

1183
        \arsort($that->array);
70 ✔
1184

1185
        return $that;
70 ✔
1186
    }
1187

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

1212
        foreach ($that->getGenerator() as $key => $value) {
21 ✔
1213
            $closure($value, $key);
21 ✔
1214
        }
1215

1216
        return static::create(
21 ✔
1217
            $that->toArray(),
21 ✔
1218
            $this->iteratorClass,
21 ✔
1219
            false
21 ✔
1220
        );
21 ✔
1221
    }
1222

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

1241
        if (!$count) {
70 ✔
1242
            return 0;
14 ✔
1243
        }
1244

1245
        if ((int) $decimals !== $decimals) {
56 ✔
1246
            $decimals = 0;
21 ✔
1247
        }
1248

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

1264
        return \round($sum / $count, $decimals);
56 ✔
1265
    }
1266

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

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

1305
            $return[$key] = $value;
7 ✔
1306
        }
1307

1308
        return static::create(
7 ✔
1309
            $return,
7 ✔
1310
            $this->iteratorClass,
7 ✔
1311
            false
7 ✔
1312
        );
7 ✔
1313
    }
1314

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

1331
        return $this;
77 ✔
1332
    }
1333

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

1359
                    $values[$key] = $value;
×
1360
                    if ($tmpCounter === $size) {
×
1361
                        yield $values;
×
1362

1363
                        $values = [];
×
1364
                        $tmpCounter = 0;
×
1365
                    }
1366
                }
1367

1368
                if ($values !== []) {
×
1369
                    yield $values;
×
1370
                }
1371
            };
×
1372
        } else {
1373
            $generator = function () use ($size) {
42 ✔
1374
                $values = [];
42 ✔
1375
                $tmpCounter = 0;
42 ✔
1376
                foreach ($this->getGenerator() as $value) {
42 ✔
1377
                    ++$tmpCounter;
42 ✔
1378

1379
                    $values[] = $value;
42 ✔
1380
                    if ($tmpCounter === $size) {
42 ✔
1381
                        yield $values;
42 ✔
1382

1383
                        $values = [];
42 ✔
1384
                        $tmpCounter = 0;
42 ✔
1385
                    }
1386
                }
1387

1388
                if ($values !== []) {
42 ✔
1389
                    yield $values;
35 ✔
1390
                }
1391
            };
42 ✔
1392
        }
1393

1394
        return static::create(
42 ✔
1395
            $generator,
42 ✔
1396
            $this->iteratorClass,
42 ✔
1397
            false
42 ✔
1398
        );
42 ✔
1399
    }
1400

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

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

1448
            return $this;
21 ✔
1449
        }
1450

1451
        $this->array = [];
49 ✔
1452
        $this->generator = null;
49 ✔
1453

1454
        return $this;
49 ✔
1455
    }
1456

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

1477
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1478
        $tmpCount = 0;
70 ✔
1479
        foreach ($this->getGeneratorByReference() as &$valueFromArray) {
70 ✔
1480
            $tmpCount++;
56 ✔
1481

1482
            if ($strict) {
56 ✔
1483
                if ($value !== $valueFromArray) {
56 ✔
1484
                    return false;
36 ✔
1485
                }
1486
            } else {
1487
                /** @noinspection NestedPositiveIfStatementsInspection */
1488
                if ($value != $valueFromArray) {
×
1489
                    return false;
×
1490
                }
1491
            }
1492
        }
1493

1494
        return $tmpCount !== 0;
49 ✔
1495
    }
1496

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

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

1531
        return false;
49 ✔
1532
    }
1533

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

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

1568
            return false;
56 ✔
1569
        }
1570

1571
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1572
        foreach ($this->getGeneratorByReference() as &$valueTmp) {
84 ✔
1573
            if (\mb_strtoupper((string) $valueTmp) === \mb_strtoupper((string) $value)) {
77 ✔
1574
                return true;
56 ✔
1575
            }
1576
        }
1577

1578
        return false;
28 ✔
1579
    }
1580

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

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

1634
        return \count(
7 ✔
1635
            \array_intersect($needles, $this->keys()->toArray()),
7 ✔
1636
            \COUNT_NORMAL
7 ✔
1637
        )
7 ✔
1638
                ===
7 ✔
1639
                \count(
7 ✔
1640
                    $needles,
7 ✔
1641
                    \COUNT_NORMAL
7 ✔
1642
                );
7 ✔
1643
    }
1644

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

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

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

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

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

1742
        return $return;
49 ✔
1743
    }
1744

1745
    /**
1746
     * Creates an Arrayy object.
1747
     *
1748
     * @param mixed  $data
1749
     * @param string $iteratorClass
1750
     * @param bool   $checkPropertiesInConstructor
1751
     *
1752
     * @return static
1753
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1754
     *
1755
     * @phpstan-param  class-string<\Arrayy\ArrayyIterator<TKey,T>> $iteratorClass
1756
     * @phpstan-return static
1757
     * @psalm-mutation-free
1758
     */
1759
    public static function create(
1760
        $data = [],
1761
        string $iteratorClass = ArrayyIterator::class,
1762
        bool $checkPropertiesInConstructor = true
1763
    ) {
1764
        /** @var static $instance */
1765
        $instance = new static( // @phpstan-ignore new.static
5,470 ✔
1766
            $data,
5,470 ✔
1767
            $iteratorClass,
5,470 ✔
1768
            $checkPropertiesInConstructor
5,470 ✔
1769
        );
5,470 ✔
1770

1771
        return $instance;
5,470 ✔
1772
    }
1773

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

1798
        if ($items === null) {
14 ✔
1799
            $items = $this->getArray();
14 ✔
1800
        }
1801

1802
        foreach ($items as $key => $value) {
14 ✔
1803
            if (\is_array($value) && $value !== []) {
14 ✔
1804
                $flatten[] = $this->flatten($delimiter, $prepend . $key . $delimiter, $value);
14 ✔
1805
            } else {
1806
                $flatten[] = [$prepend . $key => $value];
14 ✔
1807
            }
1808
        }
1809

1810
        if (\count($flatten) === 0) {
14 ✔
1811
            return [];
×
1812
        }
1813

1814
        return \array_merge_recursive([], ...$flatten);
14 ✔
1815
    }
1816

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

1835
        return $this;
197 ✔
1836
    }
1837

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

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

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

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

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

1922
        if ($object instanceof self) {
28 ✔
1923
            $objectArray = $object->getGenerator();
28 ✔
1924
        } else {
1925
            $objectArray = $object;
×
1926
        }
1927

1928
        foreach ($objectArray as $key => $value) {
28 ✔
1929
            /**
1930
             * @psalm-suppress ImpureMethodCall - object is already re-created
1931
             */
1932
            $arrayy->internalSet($key, $value);
21 ✔
1933
        }
1934

1935
        return $arrayy;
28 ✔
1936
    }
1937

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

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

1973
            if (!empty($array)) {
7 ✔
1974
                $array = $array[0];
7 ✔
1975
            }
1976
        } else {
1977
            /** @noinspection NestedPositiveIfStatementsInspection */
1978
            if ($delimiter !== null) {
63 ✔
1979
                $array = \explode($delimiter, $str);
49 ✔
1980
            } else {
1981
                $array = [$str];
14 ✔
1982
            }
1983
        }
1984

1985
        // trim all string in the array
1986
        /**
1987
         * @psalm-suppress MissingClosureParamType
1988
         */
1989
        \array_walk(
70 ✔
1990
            $array,
70 ✔
1991
            static function (&$val) {
70 ✔
1992
                if ((string) $val === $val) {
70 ✔
1993
                    $val = \trim($val);
70 ✔
1994
                }
1995
            }
70 ✔
1996
        );
70 ✔
1997

1998
        /** @var static $return - help for phpstan */
1999
        $return = static::create($array);
70 ✔
2000

2001
        return $return;
70 ✔
2002
    }
2003

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

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

2042
        return $return;
14 ✔
2043
    }
2044

2045
    /**
2046
     * Gets the element of the array at the current internal iterator position.
2047
     *
2048
     * @return false|mixed
2049
     *
2050
     * @phpstan-return false|T
2051
     */
2052
    public function current()
2053
    {
2054
        if ($this->generator) {
×
2055
            return $this->generator->current();
×
2056
        }
2057

2058
        return \current($this->array);
×
2059
    }
2060

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

2091
        \uksort($this->array, $callable);
35 ✔
2092

2093
        return $this;
35 ✔
2094
    }
2095

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

2116
        $that->generatorToArray();
7 ✔
2117

2118
        /**
2119
         * @psalm-suppress ImpureFunctionCall - object is already cloned
2120
         */
2121
        \uksort($that->array, $callable);
7 ✔
2122

2123
        return $that;
7 ✔
2124
    }
2125

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

2154
        \usort($this->array, $callable);
77 ✔
2155

2156
        return $this;
77 ✔
2157
    }
2158

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

2179
        /**
2180
         * @psalm-suppress ImpureMethodCall - object is already cloned
2181
         */
2182
        $that->customSortValues($callable);
35 ✔
2183

2184
        return $that;
35 ✔
2185
    }
2186

2187
    /**
2188
     * Delete the given key or keys.
2189
     *
2190
     * @param int|int[]|string|string[] $keyOrKeys
2191
     *
2192
     * @return void
2193
     */
2194
    public function delete($keyOrKeys)
2195
    {
2196
        $keyOrKeys = (array) $keyOrKeys;
63 ✔
2197

2198
        foreach ($keyOrKeys as $key) {
63 ✔
2199
            $this->offsetUnset($key);
63 ✔
2200
        }
2201
    }
2202

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

2227
        $generator = function () use ($array): \Generator {
91 ✔
2228
            foreach ($this->getGenerator() as $key => $value) {
91 ✔
2229
                if (\in_array($value, $array, true) === false) {
77 ✔
2230
                    yield $key => $value;
35 ✔
2231
                }
2232
            }
2233
        };
91 ✔
2234

2235
        return static::create(
91 ✔
2236
            $generator,
91 ✔
2237
            $this->iteratorClass,
91 ✔
2238
            false
91 ✔
2239
        );
91 ✔
2240
    }
2241

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

2262
        $generator = function () use ($array): \Generator {
63 ✔
2263
            foreach ($this->getGenerator() as $key => $value) {
63 ✔
2264
                if (\array_key_exists($key, $array) === false) {
56 ✔
2265
                    yield $key => $value;
14 ✔
2266
                }
2267
            }
2268
        };
63 ✔
2269

2270
        return static::create(
63 ✔
2271
            $generator,
63 ✔
2272
            $this->iteratorClass,
63 ✔
2273
            false
63 ✔
2274
        );
63 ✔
2275
    }
2276

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

2297
        $generator = function () use ($array): \Generator {
63 ✔
2298
            foreach ($this->getGenerator() as $key => $value) {
63 ✔
2299
                $isset = isset($array[$key]);
56 ✔
2300

2301
                if (
2302
                    !$isset
56 ✔
2303
                    ||
2304
                    $array[$key] !== $value
56 ✔
2305
                ) {
2306
                    yield $key => $value;
28 ✔
2307
                }
2308
            }
2309
        };
63 ✔
2310

2311
        return static::create(
63 ✔
2312
            $generator,
63 ✔
2313
            $this->iteratorClass,
63 ✔
2314
            false
63 ✔
2315
        );
63 ✔
2316
    }
2317

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

2341
        if (
2342
            $helperVariableForRecursion !== null
7 ✔
2343
            &&
2344
            \is_array($helperVariableForRecursion)
7 ✔
2345
        ) {
2346
            $arrayForTheLoop = $helperVariableForRecursion;
×
2347
        } else {
2348
            $arrayForTheLoop = $this->getGenerator();
7 ✔
2349
        }
2350

2351
        foreach ($arrayForTheLoop as $key => $value) {
7 ✔
2352
            if ($value instanceof self) {
7 ✔
2353
                $value = $value->toArray();
7 ✔
2354
            }
2355

2356
            if (\array_key_exists($key, $array)) {
7 ✔
2357
                if ($value !== $array[$key]) {
7 ✔
2358
                    $result[$key] = $value;
7 ✔
2359
                }
2360
            } else {
2361
                $result[$key] = $value;
7 ✔
2362
            }
2363
        }
2364

2365
        return static::create(
7 ✔
2366
            $result,
7 ✔
2367
            $this->iteratorClass,
7 ✔
2368
            false
7 ✔
2369
        );
7 ✔
2370
    }
2371

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

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

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

2447
        foreach ($this->getGenerator() as $key => $value) {
42 ✔
2448
            $array[$key] = $closure($value, $key);
42 ✔
2449
        }
2450

2451
        return static::create(
42 ✔
2452
            $array,
42 ✔
2453
            $this->iteratorClass,
42 ✔
2454
            false
42 ✔
2455
        );
42 ✔
2456
    }
2457

2458
    /**
2459
     * Sets the internal iterator to the last element in the array and returns this element.
2460
     *
2461
     * @return false|mixed
2462
     *
2463
     * @phpstan-return T|false
2464
     */
2465
    public function end()
2466
    {
2467
        if ($this->generator) {
×
2468
            $count = $this->count();
×
2469
            if ($count === 0) {
×
2470
                return false;
×
2471
            }
2472

2473
            $counter = 0;
×
2474
            /** @noinspection PhpUnusedLocalVariableInspection */
2475
            foreach ($this->getIterator() as $item) {
×
2476
                if (++$counter === $count - 1) {
×
2477
                    break;
×
2478
                }
2479
            }
2480
        }
2481

2482
        return \end($this->array);
×
2483
    }
2484

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

2507
        foreach ($this->getGenerator() as $key => $value) {
28 ✔
2508
            if ($closure($value, $key)) {
21 ✔
2509
                $isExists = true;
7 ✔
2510

2511
                break;
7 ✔
2512
            }
2513
        }
2514

2515
        return $isExists;
28 ✔
2516
    }
2517

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

2541
        $this->generatorToArray();
49 ✔
2542

2543
        $tmpArray = $this->array;
49 ✔
2544

2545
        $count = \count($tmpArray);
49 ✔
2546

2547
        while ($count < $num) {
49 ✔
2548
            $tmpArray[] = $default;
28 ✔
2549
            ++$count;
28 ✔
2550
        }
2551

2552
        return static::create(
49 ✔
2553
            $tmpArray,
49 ✔
2554
            $this->iteratorClass,
49 ✔
2555
            false
49 ✔
2556
        );
49 ✔
2557
    }
2558

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

2605
        if ($flag === \ARRAY_FILTER_USE_KEY) {
84 ✔
2606
            $generator = function () use ($closure) {
7 ✔
2607
                foreach ($this->getGenerator() as $key => $value) {
7 ✔
2608
                    if ($closure($key) === true) {
7 ✔
2609
                        yield $key => $value;
7 ✔
2610
                    }
2611
                }
2612
            };
7 ✔
2613
        } elseif ($flag === \ARRAY_FILTER_USE_BOTH) {
84 ✔
2614
            /** @noinspection PhpSillyAssignmentInspection - hack for phpstan - https://github.com/phpstan/phpstan/issues/4192 */
2615
            /** @phpstan-var \Closure(T,TKey):bool $closure */
2616
            $closure = $closure;
84 ✔
2617

2618
            $generator = function () use ($closure) {
84 ✔
2619
                foreach ($this->getGenerator() as $key => $value) {
77 ✔
2620
                    if ($closure($value, $key) === true) {
70 ✔
2621
                        yield $key => $value;
63 ✔
2622
                    }
2623
                }
2624
            };
84 ✔
2625
        } else {
2626
            $generator = function () use ($closure) {
7 ✔
2627
                foreach ($this->getGenerator() as $key => $value) {
7 ✔
2628
                    if ($closure($value) === true) {
7 ✔
2629
                        yield $key => $value;
7 ✔
2630
                    }
2631
                }
2632
            };
7 ✔
2633
        }
2634

2635
        return static::create(
84 ✔
2636
            $generator,
84 ✔
2637
            $this->iteratorClass,
84 ✔
2638
            false
84 ✔
2639
        );
84 ✔
2640
    }
2641

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

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

2720
        $result = \array_values(
7 ✔
2721
            \array_filter(
7 ✔
2722
                $this->toArray(false, true),
7 ✔
2723
                static function ($item) use (
7 ✔
2724
                    $property,
7 ✔
2725
                    $value,
7 ✔
2726
                    $ops,
7 ✔
2727
                    $comparisonOp
7 ✔
2728
                ) {
7 ✔
2729
                    $item = (array) $item;
7 ✔
2730
                    $itemArrayy = static::create($item);
7 ✔
2731
                    $item[$property] = $itemArrayy->get($property, []);
7 ✔
2732

2733
                    return $ops[$comparisonOp]($item, $property, $value);
7 ✔
2734
                }
7 ✔
2735
            )
7 ✔
2736
        );
7 ✔
2737

2738
        return static::create(
7 ✔
2739
            $result,
7 ✔
2740
            $this->iteratorClass,
7 ✔
2741
            false
7 ✔
2742
        );
7 ✔
2743
    }
2744

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

2772
        return false;
21 ✔
2773
    }
2774

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

2802
        return false;
28 ✔
2803
    }
2804

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

2832
    /**
2833
     * Get the first value from the current array.
2834
     *
2835
     * EXAMPLE: <code>
2836
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->first(); // 'foo'
2837
     * </code>
2838
     *
2839
     * @return mixed|null
2840
     *                    <p>Return null if there wasn't a element.</p>
2841
     *
2842
     * @phpstan-return T|null
2843
     * @psalm-mutation-free
2844
     */
2845
    public function first()
2846
    {
2847
        $key_first = $this->firstKey();
155 ✔
2848
        if ($key_first === null) {
155 ✔
2849
            return null;
21 ✔
2850
        }
2851

2852
        return $this->get($key_first);
134 ✔
2853
    }
2854

2855
    /**
2856
     * Get the first key from the current array.
2857
     *
2858
     * @return mixed|null
2859
     *                    <p>Return null if there wasn't a element.</p>
2860
     *
2861
     * @phpstan-return TKey|null
2862
     *
2863
     * @psalm-mutation-free
2864
     */
2865
    public function firstKey()
2866
    {
2867
        $this->generatorToArray();
204 ✔
2868

2869
        /** @phpstan-var TKey|null $return - help for phpstan */
2870
        $return = \array_key_first($this->array);
204 ✔
2871

2872
        return $return;
204 ✔
2873
    }
2874

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

2895
        if ($number === null) {
259 ✔
2896
            $array = (array) \array_shift($arrayTmp);
98 ✔
2897
        } else {
2898
            $array = \array_splice($arrayTmp, 0, $number);
161 ✔
2899
        }
2900

2901
        return static::create(
259 ✔
2902
            $array,
259 ✔
2903
            $this->iteratorClass,
259 ✔
2904
            false
259 ✔
2905
        );
259 ✔
2906
    }
2907

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

2924
        if ($number === null) {
21 ✔
2925
            $array = (array) \array_shift($arrayTmp);
×
2926
        } else {
2927
            $array = \array_splice($arrayTmp, 0, $number);
21 ✔
2928
        }
2929

2930
        return static::create(
21 ✔
2931
            $array,
21 ✔
2932
            $this->iteratorClass,
21 ✔
2933
            false
21 ✔
2934
        );
21 ✔
2935
    }
2936

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

2956
        if ($number === null) {
238 ✔
2957
            $shift = \array_shift($this->array);
133 ✔
2958
            /* @phpstan-ignore assign.propertyType */
2959
            $this->array = $shift !== null ? [$shift] : [];
133 ✔
2960
        } else {
2961
            $splice = \array_splice($this->array, 0, $number);
105 ✔
2962
            $this->array = $splice;
105 ✔
2963
        }
2964

2965
        return $this;
238 ✔
2966
    }
2967

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

2989
        return static::create(
7 ✔
2990
            $generator,
7 ✔
2991
            $this->iteratorClass,
7 ✔
2992
            false
7 ✔
2993
        );
7 ✔
2994
    }
2995

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

3037
            return clone $this;
7 ✔
3038
        }
3039

3040
        if ($array !== null) {
1,979 ✔
3041
            if ($useByReference) {
28 ✔
3042
                $usedArray = &$array;
×
3043
            } else {
3044
                $usedArray = $array;
28 ✔
3045
            }
3046
        } else {
3047
            $this->generatorToArray();
1,958 ✔
3048

3049
            if ($useByReference) {
1,958 ✔
3050
                $usedArray = &$this->array;
1,167 ✔
3051
            } else {
3052
                $usedArray = $this->array;
918 ✔
3053
            }
3054
        }
3055

3056
        if ($key === null) {
1,979 ✔
3057
            return static::create(
7 ✔
3058
                [],
7 ✔
3059
                $this->iteratorClass,
7 ✔
3060
                false
7 ✔
3061
            )->createByReference($usedArray);
7 ✔
3062
        }
3063

3064
        // php cast "bool"-index into "int"-index
3065
        /* @phpstan-ignore identical.alwaysFalse */
3066
        if ((bool) $key === $key) {
1,979 ✔
3067
            $key = (int) $key;
×
3068
        }
3069

3070
        if (\array_key_exists($key, $usedArray) === true) {
1,979 ✔
3071
            if (\is_array($usedArray[$key])) {
1,713 ✔
3072
                return static::create(
141 ✔
3073
                    [],
141 ✔
3074
                    $this->iteratorClass,
141 ✔
3075
                    false
141 ✔
3076
                )->createByReference($usedArray[$key]);
141 ✔
3077
            }
3078

3079
            return $usedArray[$key];
1,601 ✔
3080
        }
3081

3082
        // crawl through array, get key according to object or not
3083
        $usePath = false;
427 ✔
3084
        if (
3085
            $this->pathSeparator
427 ✔
3086
            &&
3087
            (string) $key === $key
427 ✔
3088
            &&
3089
            \strpos($key, $this->pathSeparator) !== false
427 ✔
3090
        ) {
3091
            $segments = \explode($this->pathSeparator, (string) $key);
217 ✔
3092
            $usePath = true;
217 ✔
3093
            $usedArrayTmp = $usedArray; // do not use the reference for dot-annotations
217 ✔
3094

3095
            foreach ($segments as $segment) {
217 ✔
3096
                if (
3097
                    (
3098
                        \is_array($usedArrayTmp)
217 ✔
3099
                        ||
217 ✔
3100
                        $usedArrayTmp instanceof \ArrayAccess
217 ✔
3101
                    )
3102
                    &&
3103
                    isset($usedArrayTmp[$segment])
217 ✔
3104
                ) {
3105
                    $usedArrayTmp = $usedArrayTmp[$segment];
210 ✔
3106

3107
                    continue;
210 ✔
3108
                }
3109

3110
                if (
3111
                    \is_object($usedArrayTmp) === true
98 ✔
3112
                    &&
3113
                    \property_exists($usedArrayTmp, $segment)
98 ✔
3114
                ) {
3115
                    $usedArrayTmp = $usedArrayTmp->{$segment};
7 ✔
3116

3117
                    continue;
7 ✔
3118
                }
3119

3120
                if ($segments[0] === '*') {
91 ✔
3121
                    $segmentsTmp = $segments;
7 ✔
3122
                    unset($segmentsTmp[0]);
7 ✔
3123
                    $keyTmp = \implode('.', $segmentsTmp);
7 ✔
3124
                    $returnTmp = static::create(
7 ✔
3125
                        [],
7 ✔
3126
                        $this->iteratorClass,
7 ✔
3127
                        false
7 ✔
3128
                    );
7 ✔
3129
                    foreach ($this->getAll() as $dataTmp) {
7 ✔
3130
                        if ($dataTmp instanceof self) {
7 ✔
3131
                            $returnTmp->add($dataTmp->get($keyTmp));
×
3132

3133
                            continue;
×
3134
                        }
3135

3136
                        if (
3137
                            (
3138
                                \is_array($dataTmp)
7 ✔
3139
                                ||
7 ✔
3140
                                $dataTmp instanceof \ArrayAccess
7 ✔
3141
                            )
3142
                            &&
3143
                            isset($dataTmp[$keyTmp])
7 ✔
3144
                        ) {
3145
                            $returnTmp->add($dataTmp[$keyTmp]);
×
3146

3147
                            continue;
×
3148
                        }
3149

3150
                        if (
3151
                            \is_object($dataTmp) === true
7 ✔
3152
                            &&
3153
                            \property_exists($dataTmp, $keyTmp)
7 ✔
3154
                        ) {
3155
                            $returnTmp->add($dataTmp->{$keyTmp});
7 ✔
3156

3157
                            continue;
7 ✔
3158
                        }
3159
                    }
3160

3161
                    if ($returnTmp->count() > 0) {
7 ✔
3162
                        return $returnTmp;
7 ✔
3163
                    }
3164
                }
3165

3166
                return $fallback instanceof \Closure ? $fallback() : $fallback;
84 ✔
3167
            }
3168

3169
            if (\is_array($usedArrayTmp)) {
196 ✔
3170
                return static::create(
42 ✔
3171
                    [],
42 ✔
3172
                    $this->iteratorClass,
42 ✔
3173
                    false
42 ✔
3174
                )->createByReference($usedArrayTmp);
42 ✔
3175
            }
3176

3177
            return $usedArrayTmp;
196 ✔
3178
        }
3179

3180
        if (!isset($usedArray[$key])) {
210 ✔
3181
            return $fallback instanceof \Closure ? $fallback() : $fallback;
210 ✔
3182
        }
3183

3184
        return static::create(
×
3185
            [],
×
3186
            $this->iteratorClass,
×
3187
            false
×
3188
        )->createByReference($usedArray);
×
3189
    }
3190

3191
    /**
3192
     * alias: for "Arrayy->toArray()"
3193
     *
3194
     * @return array
3195
     *
3196
     * @see          Arrayy::getArray()
3197
     *
3198
     * @phpstan-return array<TKey,T>
3199
     */
3200
    public function getAll(): array
3201
    {
3202
        /** @var array<TKey,T> $return */
3203
        $return = $this->toArray();
105 ✔
3204

3205
        return $return;
105 ✔
3206
    }
3207

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

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

3256
        $jsonObject = \json_decode($json, false);
56 ✔
3257

3258
        $mapper = new \Arrayy\Mapper\Json();
56 ✔
3259
        $mapper->undefinedPropertyHandler = static function ($object, $key, $jsonValue) use ($class) {
56 ✔
3260
            if ($class->checkPropertiesMismatchInConstructor) {
×
3261
                throw new \TypeError('Property mismatch - input: ' . \print_r(['key' => $key, 'jsonValue' => $jsonValue], true) . ' for object: ' . \get_class($object));
×
3262
            }
3263
        };
56 ✔
3264

3265
        /** @var static $return - hack for phpstan */
3266
        $return = $mapper->map($jsonObject, $class);
56 ✔
3267

3268
        return $return;
35 ✔
3269
    }
3270

3271
    /**
3272
     * @return array<array-key,TypeCheckInterface>|TypeCheckArray<array-key,TypeCheckInterface>
3273
     *
3274
     * @internal
3275
     */
3276
    public function getPhpDocPropertiesFromClass()
3277
    {
3278
        if ($this->properties === []) {
152 ✔
3279
            $this->properties = $this->getPropertiesFromPhpDoc();
82 ✔
3280
        }
3281

3282
        return $this->properties;
152 ✔
3283
    }
3284

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

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

3342
                    if ($indexKey !== null) {
7 ✔
3343
                        foreach ($value as $keyInner => $valueInner) {
7 ✔
3344
                            if ($indexKey === $keyInner) {
7 ✔
3345
                                $newKey = $valueInner;
7 ✔
3346
                            }
3347

3348
                            if ($columnKey === $keyInner) {
7 ✔
3349
                                $newValue = $valueInner;
7 ✔
3350
                                $newValueFound = true;
7 ✔
3351
                            }
3352
                        }
3353
                    } else {
3354
                        foreach ($value as $keyInner => $valueInner) {
7 ✔
3355
                            if ($columnKey === $keyInner) {
7 ✔
3356
                                $newValue = $valueInner;
7 ✔
3357
                                $newValueFound = true;
7 ✔
3358
                            }
3359
                        }
3360
                    }
3361

3362
                    if ($newValueFound === false) {
7 ✔
3363
                        if ($newKey !== null) {
7 ✔
3364
                            yield $newKey => $value;
7 ✔
3365
                        } else {
3366
                            yield $value;
7 ✔
3367
                        }
3368
                    } else {
3369
                        /** @noinspection NestedPositiveIfStatementsInspection */
3370
                        if ($newKey !== null) {
7 ✔
3371
                            yield $newKey => $newValue;
7 ✔
3372
                        } else {
3373
                            yield $newValue;
7 ✔
3374
                        }
3375
                    }
3376
                }
3377
            };
7 ✔
3378
        }
3379

3380
        return static::create(
7 ✔
3381
            $generator,
7 ✔
3382
            $this->iteratorClass,
7 ✔
3383
            false
7 ✔
3384
        );
7 ✔
3385
    }
3386

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

3401
            return;
35 ✔
3402
        }
3403

3404
        // -> false-positive -> see "&$value"
3405
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
3406
        foreach ($this->array as $key => &$value) {
490 ✔
3407
            yield $key => $value;
441 ✔
3408
        }
3409
    }
3410

3411
    /**
3412
     * Get the current array from the "Arrayy"-object as generator.
3413
     *
3414
     * @return \Generator
3415
     *
3416
     * @phpstan-return \Generator<mixed,T>|\Generator<TKey,T>
3417
     * @psalm-mutation-free
3418
     */
3419
    public function getGenerator(): \Generator
3420
    {
3421
        if ($this->generator instanceof ArrayyRewindableGenerator) {
7,873 ✔
3422
            yield from $this->generator;
546 ✔
3423

3424
            return;
546 ✔
3425
        }
3426

3427
        yield from $this->array;
7,859 ✔
3428
    }
3429

3430
    /**
3431
     * Get the current array from the "Arrayy"-object as generator.
3432
     *
3433
     * @return \Generator
3434
     *
3435
     * @phpstan-return \Generator<mixed,T>|\Generator<TKey,T>
3436
     * @psalm-mutation-free
3437
     */
3438
    public function getBackwardsGenerator(): \Generator
3439
    {
3440
        yield from $this->reverseKeepIndex();
28 ✔
3441
    }
3442

3443
    /**
3444
     * alias: for "Arrayy->keys()"
3445
     *
3446
     * @return static
3447
     *                <p>(Immutable)</p>
3448
     *
3449
     * @see          Arrayy::keys()
3450
     *
3451
     * @phpstan-return static
3452
     * @psalm-mutation-free
3453
     */
3454
    public function getKeys()
3455
    {
3456
        return $this->keys();
14 ✔
3457
    }
3458

3459
    /**
3460
     * Get the current array from the "Arrayy"-object as object.
3461
     *
3462
     * @return \stdClass
3463
     */
3464
    public function getObject(): \stdClass
3465
    {
3466
        return self::arrayToObject($this->toArray());
28 ✔
3467
    }
3468

3469
    /**
3470
     * alias: for "Arrayy->randomImmutable()"
3471
     *
3472
     * @return static
3473
     *                <p>(Immutable)</p>
3474
     *
3475
     * @see          Arrayy::randomImmutable()
3476
     *
3477
     * @phpstan-return static
3478
     */
3479
    public function getRandom(): self
3480
    {
3481
        return $this->randomImmutable();
28 ✔
3482
    }
3483

3484
    /**
3485
     * alias: for "Arrayy->randomKey()"
3486
     *
3487
     * @return mixed|null
3488
     *                    <p>Get a key/index or null if there wasn't a key/index.</p>
3489
     *
3490
     * @phpstan-return null|TKey
3491
     *
3492
     * @see Arrayy::randomKey()
3493
     */
3494
    public function getRandomKey()
3495
    {
3496
        return $this->randomKey();
21 ✔
3497
    }
3498

3499
    /**
3500
     * alias: for "Arrayy->randomKeys()"
3501
     *
3502
     * @param int $number
3503
     *
3504
     * @return static
3505
     *                <p>(Immutable)</p>
3506
     *
3507
     * @see          Arrayy::randomKeys()
3508
     *
3509
     * @phpstan-return static
3510
     */
3511
    public function getRandomKeys(int $number): self
3512
    {
3513
        return $this->randomKeys($number);
56 ✔
3514
    }
3515

3516
    /**
3517
     * alias: for "Arrayy->randomValue()"
3518
     *
3519
     * @return mixed|null
3520
     *                    <p>Get a random value or null if there wasn't a value.</p>
3521
     *
3522
     * @phpstan-return null|T
3523
     *
3524
     * @see Arrayy::randomValue()
3525
     */
3526
    public function getRandomValue()
3527
    {
3528
        return $this->randomValue();
21 ✔
3529
    }
3530

3531
    /**
3532
     * alias: for "Arrayy->randomValues()"
3533
     *
3534
     * @param int $number
3535
     *
3536
     * @return static
3537
     *                <p>(Immutable)</p>
3538
     *
3539
     * @see          Arrayy::randomValues()
3540
     *
3541
     * @phpstan-return static
3542
     */
3543
    public function getRandomValues(int $number): self
3544
    {
3545
        return $this->randomValues($number);
42 ✔
3546
    }
3547

3548
    /**
3549
     * Gets all values.
3550
     *
3551
     * @return static
3552
     *                <p>The values of all elements in this array, in the order they
3553
     *                appear in the array.</p>
3554
     *
3555
     * @phpstan-return static
3556
     */
3557
    public function getValues()
3558
    {
3559
        $this->generatorToArray(false);
28 ✔
3560

3561
        return static::create(
28 ✔
3562
            \array_values($this->array),
28 ✔
3563
            $this->iteratorClass,
28 ✔
3564
            false
28 ✔
3565
        );
28 ✔
3566
    }
3567

3568
    /**
3569
     * Gets all values via Generator.
3570
     *
3571
     * @return \Generator
3572
     *                    <p>The values of all elements in this array, in the order they
3573
     *                    appear in the array as Generator.</p>
3574
     *
3575
     * @phpstan-return \Generator<TKey,T>
3576
     */
3577
    public function getValuesYield(): \Generator
3578
    {
3579
        yield from $this->getGenerator();
28 ✔
3580
    }
3581

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

3600
        // Iterate over values, group by property/results from closure.
3601
        foreach ($this->getGenerator() as $key => $value) {
28 ✔
3602
            if (\is_callable($grouper) === true) {
28 ✔
3603
                $groupKey = $grouper($value, $key);
21 ✔
3604
            } else {
3605
                $groupKey = $this->get($grouper);
7 ✔
3606
            }
3607

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

3610
            if ($groupKey instanceof self) {
28 ✔
3611
                $groupKey = $groupKey->toArray();
×
3612
            }
3613

3614
            if ($newValue instanceof self) {
28 ✔
3615
                $newValue = $newValue->toArray();
28 ✔
3616
            }
3617

3618
            // Add to result.
3619
            if ($groupKey !== null) {
28 ✔
3620
                $result[$groupKey] = $newValue;
21 ✔
3621

3622
                if ($saveKeys) {
21 ✔
3623
                    $result[$groupKey][$key] = $value;
14 ✔
3624
                } else {
3625
                    $result[$groupKey][] = $value;
7 ✔
3626
                }
3627
            }
3628
        }
3629

3630
        return static::create(
28 ✔
3631
            $result,
28 ✔
3632
            $this->iteratorClass,
28 ✔
3633
            false
28 ✔
3634
        );
28 ✔
3635
    }
3636

3637
    /**
3638
     * Check if an array has a given key.
3639
     *
3640
     * @param mixed $key
3641
     *
3642
     * @return bool
3643
     *
3644
     * @phpstan-param null|TKey|TKey[] $key
3645
     */
3646
    public function has($key): bool
3647
    {
3648
        static $UN_FOUND = null;
210 ✔
3649

3650
        if ($UN_FOUND === null) {
210 ✔
3651
            // Generate unique string to use as marker.
3652
            $UN_FOUND = 'arrayy--' . \uniqid('arrayy', true);
7 ✔
3653
        }
3654

3655
        if (\is_array($key)) {
210 ✔
3656
            if ($key === []) {
7 ✔
3657
                return false;
×
3658
            }
3659

3660
            foreach ($key as $keyTmp) {
7 ✔
3661
                $found = ($this->get($keyTmp, $UN_FOUND) !== $UN_FOUND);
7 ✔
3662
                if ($found === false) {
7 ✔
3663
                    return false;
7 ✔
3664
                }
3665
            }
3666

3667
            return true;
7 ✔
3668
        }
3669

3670
        return $this->get($key, $UN_FOUND) !== $UN_FOUND;
203 ✔
3671
    }
3672

3673
    /**
3674
     * Check if an array has a given value.
3675
     *
3676
     * INFO: If you need to search recursive please use ```contains($value, true)```.
3677
     *
3678
     * @param mixed $value
3679
     *
3680
     * @return bool
3681
     *
3682
     * @phpstan-param T $value
3683
     */
3684
    public function hasValue($value): bool
3685
    {
3686
        return $this->contains($value);
7 ✔
3687
    }
3688

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

3707
    /**
3708
     * Implodes the keys of this array.
3709
     *
3710
     * @param string $glue
3711
     *
3712
     * @return string
3713
     * @psalm-mutation-free
3714
     */
3715
    public function implodeKeys(string $glue = ''): string
3716
    {
3717
        return $this->implode_recursive($glue, $this->toArray(), true);
56 ✔
3718
    }
3719

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

3739
        foreach ($this->getGenerator() as $a) {
28 ✔
3740
            if (\array_key_exists($key, $a) === true) {
28 ✔
3741
                $results[$a[$key]] = $a;
21 ✔
3742
            }
3743
        }
3744

3745
        return static::create(
28 ✔
3746
            $results,
28 ✔
3747
            $this->iteratorClass,
28 ✔
3748
            false
28 ✔
3749
        );
28 ✔
3750
    }
3751

3752
    /**
3753
     * alias: for "Arrayy->searchIndex()"
3754
     *
3755
     * @param mixed $value
3756
     *                     <p>The value to search for.</p>
3757
     *
3758
     * @return false|int|string
3759
     *
3760
     * @phpstan-param T $value
3761
     * @phpstan-return false|TKey
3762
     *
3763
     * @see Arrayy::searchIndex()
3764
     */
3765
    public function indexOf($value)
3766
    {
3767
        return $this->searchIndex($value);
28 ✔
3768
    }
3769

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

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

3827
        return static::create(
21 ✔
3828
            \array_values(\array_intersect($this->toArray(), $search)),
21 ✔
3829
            $this->iteratorClass,
21 ✔
3830
            false
21 ✔
3831
        );
21 ✔
3832
    }
3833

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

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

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

3897
        // If the callable has arguments, pass them.
3898
        if ($arguments) {
7 ✔
3899
            $array = \array_map($callable, $this->toArray(), $arguments);
7 ✔
3900
        } else {
3901
            $array = $this->map($callable);
7 ✔
3902
        }
3903

3904
        return static::create(
7 ✔
3905
            $array,
7 ✔
3906
            $this->iteratorClass,
7 ✔
3907
            false
7 ✔
3908
        );
7 ✔
3909
    }
3910

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

3929
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
3930
        foreach ($this->keys($recursive)->getGeneratorByReference() as &$key) {
91 ✔
3931
            if ((string) $key !== $key) {
91 ✔
3932
                return false;
77 ✔
3933
            }
3934
        }
3935

3936
        return true;
21 ✔
3937
    }
3938

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

3954
        if ($keys === null) {
315 ✔
3955
            return $this->array === [];
301 ✔
3956
        }
3957

3958
        foreach ((array) $keys as $key) {
14 ✔
3959
            if (!empty($this->get($key))) {
14 ✔
3960
                return false;
14 ✔
3961
            }
3962
        }
3963

3964
        return true;
14 ✔
3965
    }
3966

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

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

4002
        return false;
126 ✔
4003
    }
4004

4005
    /**
4006
     * Check whether array is numeric or not.
4007
     *
4008
     * @return bool
4009
     *              <p>Returns true if numeric, false otherwise.</p>
4010
     */
4011
    public function isNumeric(): bool
4012
    {
4013
        if ($this->isEmpty()) {
35 ✔
4014
            return false;
14 ✔
4015
        }
4016

4017
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4018
        foreach ($this->keys()->getGeneratorByReference() as &$key) {
28 ✔
4019
            if ((int) $key !== $key) {
28 ✔
4020
                return false;
14 ✔
4021
            }
4022
        }
4023

4024
        return true;
14 ✔
4025
    }
4026

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

4055
            if ($key !== $i) {
63 ✔
4056
                return false;
21 ✔
4057
            }
4058

4059
            ++$i;
56 ✔
4060
        }
4061

4062
        return !($i === 0);
63 ✔
4063
    }
4064

4065
    /**
4066
     * @return array
4067
     *
4068
     * @phpstan-return array<TKey,T>
4069
     */
4070
    public function jsonSerialize(): array
4071
    {
4072
        /** @var array<TKey,T> $return */
4073
        $return = $this->toArray();
14 ✔
4074

4075
        return $return;
14 ✔
4076
    }
4077

4078
    /**
4079
     * Gets the key/index of the element at the current internal iterator position.
4080
     *
4081
     * @return int|string|null
4082
     * @phpstan-return array-key|null
4083
     */
4084
    public function key()
4085
    {
4086
        if ($this->generator) {
×
4087
            return $this->generator->key();
×
4088
        }
4089

4090
        return \key($this->array);
×
4091
    }
4092

4093
    /**
4094
     * Checks if the given key exists in the provided array.
4095
     *
4096
     * INFO: This method only use "array_key_exists()" if you want to use "dot"-notation,
4097
     *       then you need to use "Arrayy->offsetExists()".
4098
     *
4099
     * @param int|string $key the key to look for
4100
     *
4101
     * @return bool
4102
     * @psalm-mutation-free
4103
     */
4104
    public function keyExists($key): bool
4105
    {
4106
        foreach ($this->getGenerator() as $keyTmp => $value) {
1,447 ✔
4107
            if ($key === $keyTmp) {
1,412 ✔
4108
                return true;
1,307 ✔
4109
            }
4110
        }
4111

4112
        return false;
1,022 ✔
4113
    }
4114

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

4150
        if ($recursive === true) {
210 ✔
4151
            $array = $this->array_keys_recursive(
28 ✔
4152
                null,
28 ✔
4153
                $search_values,
28 ✔
4154
                $strict
28 ✔
4155
            );
28 ✔
4156

4157
            return static::create(
28 ✔
4158
                $array,
28 ✔
4159
                $this->iteratorClass,
28 ✔
4160
                false
28 ✔
4161
            );
28 ✔
4162
        }
4163

4164
        // non recursive
4165

4166
        if ($search_values === null) {
203 ✔
4167
            $arrayFunction = function (): \Generator {
203 ✔
4168
                foreach ($this->getGenerator() as $key => $value) {
203 ✔
4169
                    yield $key;
189 ✔
4170
                }
4171
            };
203 ✔
4172
        } else {
4173
            $arrayFunction = function () use ($search_values, $strict): \Generator {
7 ✔
4174
                $is_array_tmp = \is_array($search_values);
7 ✔
4175

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

4207
        return static::create(
203 ✔
4208
            $arrayFunction,
203 ✔
4209
            $this->iteratorClass,
203 ✔
4210
            false
203 ✔
4211
        );
203 ✔
4212
    }
4213

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

4232
        \krsort($this->array, $sort_flags);
28 ✔
4233

4234
        return $this;
28 ✔
4235
    }
4236

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

4256
        /**
4257
         * @psalm-suppress ImpureMethodCall - object is already cloned
4258
         */
4259
        $that->krsort($sort_flags);
28 ✔
4260

4261
        return $that;
28 ✔
4262
    }
4263

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

4284
        /** @var T $value_last */
4285
        $value_last = $this->get($key_last);
105 ✔
4286

4287
        return $value_last;
105 ✔
4288
    }
4289

4290
    /**
4291
     * Get the last key from the current array.
4292
     *
4293
     * @return mixed|null
4294
     *                    <p>Return null if there wasn't a element.</p>
4295
     *
4296
     * @phpstan-return null|TKey
4297
     * @psalm-mutation-free
4298
     */
4299
    public function lastKey()
4300
    {
4301
        $this->generatorToArray();
147 ✔
4302

4303
        return \array_key_last($this->array);
147 ✔
4304
    }
4305

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

4331
        if ($number === null) {
84 ✔
4332
            $poppedValue = $this->last();
56 ✔
4333

4334
            if ($poppedValue === null) {
56 ✔
4335
                $poppedValue = [$poppedValue];
7 ✔
4336
            } else {
4337
                $poppedValue = (array) $poppedValue;
49 ✔
4338
            }
4339

4340
            $arrayy = static::create(
56 ✔
4341
                $poppedValue,
56 ✔
4342
                $this->iteratorClass,
56 ✔
4343
                false
56 ✔
4344
            );
56 ✔
4345
        } else {
4346
            $arrayy = $this->rest(-$number);
28 ✔
4347
        }
4348

4349
        return $arrayy;
84 ✔
4350
    }
4351

4352
    /**
4353
     * Get the last value(s) from the current array.
4354
     *
4355
     * EXAMPLE: <code>
4356
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->lasts(2); // Arrayy[0 => 'bar', 1 => 'lall']
4357
     * </code>
4358
     *
4359
     * @param int|null $number
4360
     *
4361
     * @return $this
4362
     *               <p>(Mutable)</p>
4363
     *
4364
     * @phpstan-return static
4365
     */
4366
    public function lastsMutable(?int $number = null): self
4367
    {
4368
        if ($this->isEmpty()) {
91 ✔
4369
            return $this;
7 ✔
4370
        }
4371

4372
        $this->array = $this->lastsImmutable($number)->toArray();
84 ✔
4373
        $this->generator = null;
84 ✔
4374

4375
        return $this;
84 ✔
4376
    }
4377

4378
    /**
4379
     * Count the values from the current array.
4380
     *
4381
     * alias: for "Arrayy->count()"
4382
     *
4383
     * @param int $mode
4384
     *
4385
     * @return int
4386
     *
4387
     * @see Arrayy::count()
4388
     */
4389
    public function length(int $mode = \COUNT_NORMAL): int
4390
    {
4391
        return $this->count($mode);
140 ✔
4392
    }
4393

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

4426
        return static::create(
49 ✔
4427
            function () use ($useArguments, $callable, $useKeyAsSecondParameter, $arguments) {
49 ✔
4428
                foreach ($this->getGenerator() as $key => $value) {
49 ✔
4429
                    if ($useArguments) {
42 ✔
4430
                        if ($useKeyAsSecondParameter) {
21 ✔
4431
                            yield $key => $callable($value, $key, ...$arguments);
×
4432
                        } else {
4433
                            yield $key => $callable($value, ...$arguments);
21 ✔
4434
                        }
4435
                    } else {
4436
                        /** @noinspection NestedPositiveIfStatementsInspection */
4437
                        if ($useKeyAsSecondParameter) {
42 ✔
4438
                            yield $key => $callable($value, $key);
×
4439
                        } else {
4440
                            yield $key => $callable($value);
42 ✔
4441
                        }
4442
                    }
4443
                }
4444
            },
49 ✔
4445
            $this->iteratorClass,
49 ✔
4446
            false
49 ✔
4447
        );
49 ✔
4448
    }
4449

4450
    /**
4451
     * Check if all items in current array match a truth test.
4452
     *
4453
     * EXAMPLE: <code>
4454
     * $closure = function ($value, $key) {
4455
     *     return ($value % 2 === 0);
4456
     * };
4457
     * a([2, 4, 8])->matches($closure); // true
4458
     * </code>
4459
     *
4460
     * @param \Closure $closure
4461
     *
4462
     * @return bool
4463
     *
4464
     * @phpstan-param \Closure(T,TKey):bool $closure
4465
     */
4466
    public function matches(\Closure $closure): bool
4467
    {
4468
        if ($this->count() === 0) {
105 ✔
4469
            return false;
14 ✔
4470
        }
4471

4472
        foreach ($this->getGenerator() as $key => $value) {
91 ✔
4473
            $value = $closure($value, $key);
91 ✔
4474

4475
            if ($value === false) {
91 ✔
4476
                return false;
49 ✔
4477
            }
4478
        }
4479

4480
        return true;
49 ✔
4481
    }
4482

4483
    /**
4484
     * Check if any item in the current array matches a truth test.
4485
     *
4486
     * EXAMPLE: <code>
4487
     * $closure = function ($value, $key) {
4488
     *     return ($value % 2 === 0);
4489
     * };
4490
     * a([1, 4, 7])->matches($closure); // true
4491
     * </code>
4492
     *
4493
     * @param \Closure $closure
4494
     *
4495
     * @return bool
4496
     *
4497
     * @phpstan-param \Closure(T,TKey):bool $closure
4498
     */
4499
    public function matchesAny(\Closure $closure): bool
4500
    {
4501
        if ($this->count() === 0) {
98 ✔
4502
            return false;
14 ✔
4503
        }
4504

4505
        foreach ($this->getGenerator() as $key => $value) {
84 ✔
4506
            $value = $closure($value, $key);
84 ✔
4507

4508
            if ($value === true) {
84 ✔
4509
                return true;
63 ✔
4510
            }
4511
        }
4512

4513
        return false;
28 ✔
4514
    }
4515

4516
    /**
4517
     * Get the max value from an array.
4518
     *
4519
     * EXAMPLE: <code>
4520
     * a([-9, -8, -7, 1.32])->max(); // 1.32
4521
     * </code>
4522
     *
4523
     * @return false|float|int|string
4524
     *                                <p>Will return false if there are no values.</p>
4525
     */
4526
    public function max()
4527
    {
4528
        if ($this->count() === 0) {
77 ✔
4529
            return false;
7 ✔
4530
        }
4531

4532
        $max = false;
70 ✔
4533
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4534
        foreach ($this->getGeneratorByReference() as &$value) {
70 ✔
4535
            if (
4536
                $max === false
70 ✔
4537
                ||
4538
                $value > $max
70 ✔
4539
            ) {
4540
                $max = $value;
70 ✔
4541
            }
4542
        }
4543

4544
        return $max;
70 ✔
4545
    }
4546

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

4581
        return static::create(
231 ✔
4582
            $result,
231 ✔
4583
            $this->iteratorClass,
231 ✔
4584
            false
231 ✔
4585
        );
231 ✔
4586
    }
4587

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

4623
        return static::create(
140 ✔
4624
            $result,
140 ✔
4625
            $this->iteratorClass,
140 ✔
4626
            false
140 ✔
4627
        );
140 ✔
4628
    }
4629

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

4664
        return static::create(
119 ✔
4665
            $result,
119 ✔
4666
            $this->iteratorClass,
119 ✔
4667
            false
119 ✔
4668
        );
119 ✔
4669
    }
4670

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

4706
        return static::create(
147 ✔
4707
            $result,
147 ✔
4708
            $this->iteratorClass,
147 ✔
4709
            false
147 ✔
4710
        );
147 ✔
4711
    }
4712

4713
    /**
4714
     * Return a meta object with property names from phpdoc array-shape annotations,
4715
     * `@property` tags, and native declared properties.
4716
     *
4717
     * @return ArrayyMeta|mixed|static
4718
     */
4719
    public static function meta()
4720
    {
4721
        return (new ArrayyMeta())->getMetaObject(static::class);
299 ✔
4722
    }
4723

4724
    /**
4725
     * Get the min value from an array.
4726
     *
4727
     * EXAMPLE: <code>
4728
     * a([-9, -8, -7, 1.32])->min(); // -9
4729
     * </code>
4730
     *
4731
     * @return false|mixed
4732
     *                     <p>Will return false if there are no values.</p>
4733
     *
4734
     * @phpstan-return false|T
4735
     */
4736
    public function min()
4737
    {
4738
        if ($this->count() === 0) {
77 ✔
4739
            return false;
7 ✔
4740
        }
4741

4742
        $min = false;
70 ✔
4743
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4744
        foreach ($this->getGeneratorByReference() as &$value) {
70 ✔
4745
            if (
4746
                $min === false
70 ✔
4747
                ||
4748
                $value < $min
70 ✔
4749
            ) {
4750
                $min = $value;
70 ✔
4751
            }
4752
        }
4753

4754
        return $min;
70 ✔
4755
    }
4756

4757
    /**
4758
     * Get the most used value from the array.
4759
     *
4760
     * @return mixed|null
4761
     *                    <p>(Immutable) Return null if there wasn't an element.</p>
4762
     *
4763
     * @phpstan-return T|null
4764
     * @psalm-mutation-free
4765
     */
4766
    public function mostUsedValue()
4767
    {
4768
        /* @phpstan-ignore return.type */
4769
        return $this->countValues()->arsortImmutable()->firstKey();
21 ✔
4770
    }
4771

4772
    /**
4773
     * Get the most used value from the array.
4774
     *
4775
     * @param int|null $number <p>How many values you will take?</p>
4776
     *
4777
     * @return static
4778
     *                <p>(Immutable)</p>
4779
     *
4780
     * @phpstan-return static
4781
     * @psalm-mutation-free
4782
     */
4783
    public function mostUsedValues(?int $number = null): self
4784
    {
4785
        return $this->countValues()->arsortImmutable()->firstsKeys($number);
21 ✔
4786
    }
4787

4788
    /**
4789
     * Move an array element to a new index.
4790
     *
4791
     * EXAMPLE: <code>
4792
     * $arr2 = new A(['A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', 'E' => 'e']);
4793
     * $newArr2 = $arr2->moveElement('D', 1); // Arrayy['A' => 'a', 'D' => 'd', 'B' => 'b', 'C' => 'c', 'E' => 'e']
4794
     * </code>
4795
     *
4796
     * @param int|string $from
4797
     * @param int        $to
4798
     *
4799
     * @return static
4800
     *                <p>(Immutable)</p>
4801
     *
4802
     * @phpstan-return static
4803
     * @psalm-mutation-free
4804
     */
4805
    public function moveElement($from, $to): self
4806
    {
4807
        $array = $this->toArray();
7 ✔
4808

4809
        if ((int) $from === $from) {
7 ✔
4810
            $tmp = \array_splice($array, $from, 1);
7 ✔
4811
            \array_splice($array, (int) $to, 0, $tmp);
7 ✔
4812
            $output = $array;
7 ✔
4813
        } elseif ((string) $from === $from) {
7 ✔
4814
            $indexToMove = \array_search($from, \array_keys($array), true);
7 ✔
4815
            $itemToMove = $array[$from];
7 ✔
4816
            if ($indexToMove !== false) {
7 ✔
4817
                \array_splice($array, $indexToMove, 1);
7 ✔
4818
            }
4819
            $i = 0;
7 ✔
4820
            $output = [];
7 ✔
4821
            foreach ($array as $key => $item) {
7 ✔
4822
                if ($i === $to) {
7 ✔
4823
                    $output[$from] = $itemToMove;
7 ✔
4824
                }
4825
                $output[$key] = $item;
7 ✔
4826
                ++$i;
7 ✔
4827
            }
4828
        } else {
4829
            $output = [];
×
4830
        }
4831

4832
        return static::create(
7 ✔
4833
            $output,
7 ✔
4834
            $this->iteratorClass,
7 ✔
4835
            false
7 ✔
4836
        );
7 ✔
4837
    }
4838

4839
    /**
4840
     * Move an array element to the first place.
4841
     *
4842
     * INFO: Instead of "Arrayy->moveElement()" this method will NOT
4843
     *       loss the keys of an indexed array.
4844
     *
4845
     * @param int|string $key
4846
     *
4847
     * @return static
4848
     *                <p>(Immutable)</p>
4849
     *
4850
     * @phpstan-return static
4851
     * @psalm-mutation-free
4852
     */
4853
    public function moveElementToFirstPlace($key): self
4854
    {
4855
        $array = $this->toArray();
7 ✔
4856

4857
        if ($this->offsetExists($key)) {
7 ✔
4858
            $tmpValue = $this->get($key);
7 ✔
4859
            unset($array[$key]);
7 ✔
4860
            $array = [$key => $tmpValue] + $array;
7 ✔
4861
        }
4862

4863
        return static::create(
7 ✔
4864
            $array,
7 ✔
4865
            $this->iteratorClass,
7 ✔
4866
            false
7 ✔
4867
        );
7 ✔
4868
    }
4869

4870
    /**
4871
     * Move an array element to the last place.
4872
     *
4873
     * INFO: Instead of "Arrayy->moveElement()" this method will NOT
4874
     *       loss the keys of an indexed array.
4875
     *
4876
     * @param int|string $key
4877
     *
4878
     * @return static
4879
     *                <p>(Immutable)</p>
4880
     *
4881
     * @phpstan-return static
4882
     * @psalm-mutation-free
4883
     */
4884
    public function moveElementToLastPlace($key): self
4885
    {
4886
        $array = $this->toArray();
7 ✔
4887

4888
        if ($this->offsetExists($key)) {
7 ✔
4889
            $tmpValue = $this->get($key);
7 ✔
4890
            unset($array[$key]);
7 ✔
4891
            $array += [$key => $tmpValue];
7 ✔
4892
        }
4893

4894
        return static::create(
7 ✔
4895
            $array,
7 ✔
4896
            $this->iteratorClass,
7 ✔
4897
            false
7 ✔
4898
        );
7 ✔
4899
    }
4900

4901
    /**
4902
     * Moves the internal iterator position to the next element and returns this element.
4903
     *
4904
     * @return false|mixed
4905
     *                     <p>(Mutable) Will return false if there are no values.</p>
4906
     *
4907
     * @phpstan-return false|T
4908
     */
4909
    public function next()
4910
    {
4911
        if ($this->generator) {
×
4912
            $this->generator->next();
×
4913

4914
            return $this->generator->current() ?? false;
×
4915
        }
4916

4917
        return \next($this->array);
×
4918
    }
4919

4920
    /**
4921
     * Get the next nth keys and values from the array.
4922
     *
4923
     * @param int $step
4924
     * @param int $offset
4925
     *
4926
     * @return static
4927
     *                <p>(Immutable)</p>
4928
     *
4929
     * @phpstan-return static
4930
     * @psalm-mutation-free
4931
     */
4932
    public function nth(int $step, int $offset = 0): self
4933
    {
4934
        $arrayFunction = function () use ($step, $offset): \Generator {
7 ✔
4935
            $position = 0;
7 ✔
4936
            foreach ($this->getGenerator() as $key => $value) {
7 ✔
4937
                if ($position++ % $step !== $offset) {
7 ✔
4938
                    continue;
7 ✔
4939
                }
4940

4941
                yield $key => $value;
7 ✔
4942
            }
4943
        };
7 ✔
4944

4945
        return static::create(
7 ✔
4946
            $arrayFunction,
7 ✔
4947
            $this->iteratorClass,
7 ✔
4948
            false
7 ✔
4949
        );
7 ✔
4950
    }
4951

4952
    /**
4953
     * Get a subset of the items from the given array.
4954
     *
4955
     * @param int[]|string[] $keys
4956
     *
4957
     * @return static
4958
     *                <p>(Immutable)</p>
4959
     *
4960
     * @phpstan-param array-key[] $keys
4961
     * @phpstan-return static
4962
     * @psalm-mutation-free
4963
     */
4964
    public function only(array $keys): self
4965
    {
4966
        $keys = \array_flip($keys);
7 ✔
4967

4968
        $generator = function () use ($keys): \Generator {
7 ✔
4969
            foreach ($this->getGenerator() as $key => $value) {
7 ✔
4970
                if (isset($keys[$key])) {
7 ✔
4971
                    yield $key => $value;
7 ✔
4972
                }
4973
            }
4974
        };
7 ✔
4975

4976
        return static::create(
7 ✔
4977
            $generator,
7 ✔
4978
            $this->iteratorClass,
7 ✔
4979
            false
7 ✔
4980
        );
7 ✔
4981
    }
4982

4983
    /**
4984
     * Pad array to the specified size with a given value.
4985
     *
4986
     * @param int   $size  <p>Size of the result array.</p>
4987
     * @param mixed $value <p>Empty value by default.</p>
4988
     *
4989
     * @return static
4990
     *                <p>(Immutable) Arrayy object padded to $size with $value.</p>
4991
     *
4992
     * @phpstan-return static
4993
     * @psalm-mutation-free
4994
     */
4995
    public function pad(int $size, $value): self
4996
    {
4997
        return static::create(
35 ✔
4998
            \array_pad($this->toArray(), $size, $value),
35 ✔
4999
            $this->iteratorClass,
35 ✔
5000
            false
35 ✔
5001
        );
35 ✔
5002
    }
5003

5004
    /**
5005
     * Partitions this array in two array according to a predicate.
5006
     * Keys are preserved in the resulting array.
5007
     *
5008
     * @param \Closure $closure
5009
     *                          <p>The predicate on which to partition.</p>
5010
     *
5011
     * @return array<int, static>
5012
     *                    <p>An array with two elements. The first element contains the array
5013
     *                    of elements where the predicate returned TRUE, the second element
5014
     *                    contains the array of elements where the predicate returned FALSE.</p>
5015
     *
5016
     * @phpstan-param \Closure(T,TKey):bool $closure
5017
     * @phpstan-return array<int, static>
5018
     */
5019
    public function partition(\Closure $closure): array
5020
    {
5021
        // init
5022
        $matches = [];
7 ✔
5023
        $noMatches = [];
7 ✔
5024

5025
        foreach ($this->getGenerator() as $key => $value) {
7 ✔
5026
            if ($closure($value, $key)) {
7 ✔
5027
                $matches[$key] = $value;
7 ✔
5028
            } else {
5029
                $noMatches[$key] = $value;
7 ✔
5030
            }
5031
        }
5032

5033
        return [self::create($matches), self::create($noMatches)];
7 ✔
5034
    }
5035

5036
    /**
5037
     * Pop a specified value off the end of the current array.
5038
     *
5039
     * @return mixed|null
5040
     *                    <p>(Mutable) The popped element from the current array or null if the array is e.g. empty.</p>
5041
     *
5042
     * @phpstan-return T|null
5043
     */
5044
    public function pop()
5045
    {
5046
        $this->generatorToArray();
35 ✔
5047

5048
        return \array_pop($this->array);
35 ✔
5049
    }
5050

5051
    /**
5052
     * Prepend a (key) + value to the current array.
5053
     *
5054
     * EXAMPLE: <code>
5055
     * a(['fΓ²Γ΄' => 'bΓ Ε™'])->prepend('foo'); // Arrayy[0 => 'foo', 'fΓ²Γ΄' => 'bΓ Ε™']
5056
     * </code>
5057
     *
5058
     * @param mixed $value
5059
     * @param mixed $key
5060
     *
5061
     * @return $this
5062
     *               <p>(Mutable) Return this Arrayy object, with the prepended value.</p>
5063
     *
5064
     * @phpstan-param T $value
5065
     * @phpstan-param TKey|null $key
5066
     * @phpstan-return static
5067
     */
5068
    public function prepend($value, $key = null)
5069
    {
5070
        $this->generatorToArray();
84 ✔
5071

5072
        if ($this->properties !== []) {
84 ✔
5073
            $this->checkType($key, $value);
28 ✔
5074
        }
5075

5076
        if ($key === null) {
70 ✔
5077
            \array_unshift($this->array, $value);
56 ✔
5078
        } else {
5079
            $this->array = [$key => $value] + $this->array; // @phpstan-ignore assign.propertyType
21 ✔
5080
        }
5081

5082
        return $this;
70 ✔
5083
    }
5084

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

5110
            if ($key !== null) {
7 ✔
5111
                yield $key => $value;
×
5112
            } else {
5113
                yield $value;
7 ✔
5114
            }
5115

5116
            foreach ($this->getGenerator() as $keyOld => $itemOld) {
7 ✔
5117
                yield $keyOld => $itemOld;
7 ✔
5118
            }
5119
        };
7 ✔
5120

5121
        return static::create(
7 ✔
5122
            $generator,
7 ✔
5123
            $this->iteratorClass,
7 ✔
5124
            false
7 ✔
5125
        );
7 ✔
5126
    }
5127

5128
    /**
5129
     * Add a suffix to each key.
5130
     *
5131
     * @param float|int|string $suffix
5132
     *
5133
     * @return static
5134
     *                <p>(Immutable) Return an Arrayy object, with the prepended keys.</p>
5135
     *
5136
     * @phpstan-return static
5137
     * @psalm-mutation-free
5138
     */
5139
    public function prependToEachKey($suffix): self
5140
    {
5141
        // init
5142
        $result = [];
70 ✔
5143

5144
        foreach ($this->getGenerator() as $key => $item) {
70 ✔
5145
            if ($item instanceof self) {
63 ✔
5146
                $result[$key] = $item->prependToEachKey($suffix);
×
5147
            } elseif (\is_array($item)) {
63 ✔
5148
                $result[$key] = self::create(
×
5149
                    $item,
×
5150
                    $this->iteratorClass,
×
5151
                    false
×
5152
                )->prependToEachKey($suffix)
×
5153
                    ->toArray();
×
5154
            } else {
5155
                $result[$key . $suffix] = $item;
63 ✔
5156
            }
5157
        }
5158

5159
        return self::create(
70 ✔
5160
            $result,
70 ✔
5161
            $this->iteratorClass,
70 ✔
5162
            false
70 ✔
5163
        );
70 ✔
5164
    }
5165

5166
    /**
5167
     * Add a suffix to each value.
5168
     *
5169
     * @param float|int|string $suffix
5170
     *
5171
     * @return static
5172
     *                <p>(Immutable) Return an Arrayy object, with the prepended values.</p>
5173
     *
5174
     * @phpstan-return static
5175
     * @psalm-mutation-free
5176
     */
5177
    public function prependToEachValue($suffix): self
5178
    {
5179
        // init
5180
        $result = [];
70 ✔
5181

5182
        foreach ($this->getGenerator() as $key => $item) {
70 ✔
5183
            if ($item instanceof self) {
63 ✔
5184
                $result[$key] = $item->prependToEachValue($suffix);
×
5185
            } elseif (\is_array($item)) {
63 ✔
5186
                $result[$key] = self::create(
×
5187
                    $item,
×
5188
                    $this->iteratorClass,
×
5189
                    false
×
5190
                )->prependToEachValue($suffix)
×
5191
                    ->toArray();
×
5192
            } elseif (\is_object($item) === true) {
63 ✔
5193
                $result[$key] = $item;
7 ✔
5194
            } else {
5195
                $result[$key] = $item . $suffix;
56 ✔
5196
            }
5197
        }
5198

5199
        return self::create(
70 ✔
5200
            $result,
70 ✔
5201
            $this->iteratorClass,
70 ✔
5202
            false
70 ✔
5203
        );
70 ✔
5204
    }
5205

5206
    /**
5207
     * Return the value of a given key and
5208
     * delete the key.
5209
     *
5210
     * @param int|int[]|string|string[]|null $keyOrKeys
5211
     * @param mixed                          $fallback
5212
     *
5213
     * @return mixed
5214
     *
5215
     * @template TFallback $fallback
5216
     * @phpstan-param TFallback $fallback
5217
     * @phpstan-return TFallback|T|T[]
5218
     */
5219
    public function pull($keyOrKeys = null, $fallback = null)
5220
    {
5221
        if ($keyOrKeys === null) {
42 ✔
5222
            $array = $this->toArray();
7 ✔
5223
            $this->clear();
7 ✔
5224

5225
            return $array;
7 ✔
5226
        }
5227

5228
        if (\is_array($keyOrKeys)) {
35 ✔
5229
            $valueOrValues = [];
7 ✔
5230
            foreach ($keyOrKeys as $key) {
7 ✔
5231
                $valueOrValues[] = $this->get($key, $fallback);
7 ✔
5232
                $this->offsetUnset($key);
7 ✔
5233
            }
5234
        } else {
5235
            $valueOrValues = $this->get($keyOrKeys, $fallback);
35 ✔
5236
            $this->offsetUnset($keyOrKeys);
35 ✔
5237
        }
5238

5239
        /** @var T|T[]|TFallback $valueOrValues */
5240
        return $valueOrValues;
35 ✔
5241
    }
5242

5243
    /**
5244
     * Push one or more values onto the end of array at once.
5245
     *
5246
     * @param mixed ...$args
5247
     *
5248
     * @return $this
5249
     *               <p>(Mutable) Return this Arrayy object, with pushed elements to the end of array.</p>
5250
     *
5251
     * @noinspection ReturnTypeCanBeDeclaredInspection
5252
     *
5253
     * @phpstan-param  array<TKey,T> ...$args
5254
     * @phpstan-return static
5255
     */
5256
    public function push(...$args)
5257
    {
5258
        $this->generatorToArray();
63 ✔
5259

5260
        if (
5261
            $this->checkPropertyTypes
63 ✔
5262
            &&
5263
            $this->properties !== []
63 ✔
5264
        ) {
5265
            foreach ($args as $key => $value) {
21 ✔
5266
                $this->checkType($key, $value);
21 ✔
5267
            }
5268
        }
5269

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

5272
        return $this;
56 ✔
5273
    }
5274

5275
    /**
5276
     * Get a random value from the current array.
5277
     *
5278
     * EXAMPLE: <code>
5279
     * a([1, 2, 3, 4])->randomImmutable(2); // e.g.: Arrayy[1, 4]
5280
     * </code>
5281
     *
5282
     * @param int|null $number <p>How many values you will take?</p>
5283
     *
5284
     * @return static
5285
     *                <p>(Immutable)</p>
5286
     *
5287
     * @phpstan-return static
5288
     */
5289
    public function randomImmutable(?int $number = null): self
5290
    {
5291
        $this->generatorToArray();
133 ✔
5292

5293
        if ($this->count() === 0) {
133 ✔
5294
            return static::create(
7 ✔
5295
                [],
7 ✔
5296
                $this->iteratorClass,
7 ✔
5297
                false
7 ✔
5298
            );
7 ✔
5299
        }
5300

5301
        if ($number === null) {
126 ✔
5302
            $arrayRandValue = [$this->array[\array_rand($this->array)]];
91 ✔
5303

5304
            return static::create(
91 ✔
5305
                $arrayRandValue,
91 ✔
5306
                $this->iteratorClass,
91 ✔
5307
                false
91 ✔
5308
            );
91 ✔
5309
        }
5310

5311
        $arrayTmp = $this->array;
42 ✔
5312
        \shuffle($arrayTmp);
42 ✔
5313

5314
        return static::create(
42 ✔
5315
            $arrayTmp,
42 ✔
5316
            $this->iteratorClass,
42 ✔
5317
            false
42 ✔
5318
        )->firstsImmutable($number);
42 ✔
5319
    }
5320

5321
    /**
5322
     * Pick a random key/index from the keys of this array.
5323
     *
5324
     * EXAMPLE: <code>
5325
     * $arrayy = A::create([1 => 'one', 2 => 'two']);
5326
     * $arrayy->randomKey(); // e.g. 2
5327
     * </code>
5328
     *
5329
     * @throws \RangeException If array is empty
5330
     *
5331
     * @return mixed|null
5332
     *                    <p>Get a key/index or null if there wasn't a key/index.</p>
5333
     *
5334
     * @phpstan-return null|TKey
5335
     */
5336
    public function randomKey()
5337
    {
5338
        $result = $this->randomKeys(1);
28 ✔
5339

5340
        if (!isset($result[0])) {
28 ✔
5341
            $result[0] = null;
×
5342
        }
5343

5344
        return $result[0];
28 ✔
5345
    }
5346

5347
    /**
5348
     * Pick a given number of random keys/indexes out of this array.
5349
     *
5350
     * EXAMPLE: <code>
5351
     * a([1 => 'one', 2 => 'two'])->randomKeys(); // e.g. Arrayy[1, 2]
5352
     * </code>
5353
     *
5354
     * @param int $number <p>The number of keys/indexes (should be <= \count($this->array))</p>
5355
     *
5356
     * @throws \RangeException If array is empty
5357
     *
5358
     * @return static
5359
     *                <p>(Immutable)</p>
5360
     *
5361
     * @phpstan-return static
5362
     */
5363
    public function randomKeys(int $number): self
5364
    {
5365
        $this->generatorToArray();
91 ✔
5366

5367
        $count = $this->count();
91 ✔
5368

5369
        if (
5370
            $number === 0
91 ✔
5371
            ||
5372
            $number > $count
91 ✔
5373
        ) {
5374
            throw new \RangeException(
14 ✔
5375
                \sprintf(
14 ✔
5376
                    'Number of requested keys (%s) must be equal or lower than number of elements in this array (%s)',
14 ✔
5377
                    $number,
14 ✔
5378
                    $count
14 ✔
5379
                )
14 ✔
5380
            );
14 ✔
5381
        }
5382

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

5385
        return static::create(
77 ✔
5386
            $result,
77 ✔
5387
            $this->iteratorClass,
77 ✔
5388
            false
77 ✔
5389
        );
77 ✔
5390
    }
5391

5392
    /**
5393
     * Get a random value from the current array.
5394
     *
5395
     * EXAMPLE: <code>
5396
     * a([1, 2, 3, 4])->randomMutable(2); // e.g.: Arrayy[1, 4]
5397
     * </code>
5398
     *
5399
     * @param int|null $number <p>How many values you will take?</p>
5400
     *
5401
     * @return $this
5402
     *               <p>(Mutable) Return this Arrayy object.</p>
5403
     *
5404
     * @phpstan-return static
5405
     */
5406
    public function randomMutable(?int $number = null): self
5407
    {
5408
        $this->generatorToArray();
119 ✔
5409

5410
        if ($this->count() === 0) {
119 ✔
5411
            return static::create(
×
5412
                [],
×
5413
                $this->iteratorClass,
×
5414
                false
×
5415
            );
×
5416
        }
5417

5418
        if ($number === null) {
119 ✔
5419
            $arrayRandValue = [$this->array[\array_rand($this->array)]];
49 ✔
5420
            $this->array = $arrayRandValue; // @phpstan-ignore assign.propertyType
49 ✔
5421

5422
            return $this;
49 ✔
5423
        }
5424

5425
        \shuffle($this->array);
77 ✔
5426

5427
        return $this->firstsMutable($number);
77 ✔
5428
    }
5429

5430
    /**
5431
     * Pick a random value from the values of this array.
5432
     *
5433
     * EXAMPLE: <code>
5434
     * a([1 => 'one', 2 => 'two'])->randomValue(); // e.g. 'one'
5435
     * </code>
5436
     *
5437
     * @return mixed
5438
     *               <p>Get a random value or null if there wasn't a value.</p>
5439
     *
5440
     * @phpstan-return T|null
5441
     */
5442
    public function randomValue()
5443
    {
5444
        $result = $this->randomImmutable();
28 ✔
5445

5446
        if (!isset($result[0])) {
28 ✔
5447
            $result[0] = null;
×
5448
        }
5449

5450
        return $result[0];
28 ✔
5451
    }
5452

5453
    /**
5454
     * Pick a given number of random values out of this array.
5455
     *
5456
     * EXAMPLE: <code>
5457
     * a([1 => 'one', 2 => 'two'])->randomValues(); // e.g. Arrayy['one', 'two']
5458
     * </code>
5459
     *
5460
     * @param int $number
5461
     *
5462
     * @return static
5463
     *                <p>(Mutable)</p>
5464
     *
5465
     * @phpstan-return static
5466
     */
5467
    public function randomValues(int $number): self
5468
    {
5469
        return $this->randomMutable($number);
49 ✔
5470
    }
5471

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

5493
        foreach ($array as $option => $weight) {
63 ✔
5494
            if ($this->searchIndex($option) !== false) {
63 ✔
5495
                for ($i = 0; $i < $weight; ++$i) {
14 ✔
5496
                    $options[] = $option;
7 ✔
5497
                }
5498
            }
5499
        }
5500

5501
        return $this->mergeAppendKeepIndex($options)->randomImmutable($number);
63 ✔
5502
    }
5503

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

5537
        /** @var static $return - help for phpstan */
5538
        $return = static::create(
126 ✔
5539
            $initial,
126 ✔
5540
            $this->iteratorClass,
126 ✔
5541
            false
126 ✔
5542
        );
126 ✔
5543

5544
        return $return;
126 ✔
5545
    }
5546

5547
    /**
5548
     * @param bool $unique
5549
     *
5550
     * @return static
5551
     *                <p>(Immutable)</p>
5552
     *
5553
     * @phpstan-return static
5554
     * @psalm-mutation-free
5555
     */
5556
    public function reduce_dimension(bool $unique = true): self
5557
    {
5558
        // init
5559
        $result = [];
98 ✔
5560

5561
        foreach ($this->getGenerator() as $val) {
98 ✔
5562
            if (\is_array($val)) {
84 ✔
5563
                $result[] = static::create($val)->reduce_dimension($unique)->toArray();
35 ✔
5564
            } else {
5565
                $result[] = [$val];
84 ✔
5566
            }
5567
        }
5568

5569
        $result = $result === [] ? [] : \array_merge(...$result);
98 ✔
5570

5571
        $resultArrayy = static::create($result);
98 ✔
5572

5573
        /**
5574
         * @psalm-suppress ImpureMethodCall - object is already re-created
5575
         * @psalm-suppress InvalidReturnStatement - why?
5576
         */
5577
        return $unique ? $resultArrayy->unique() : $resultArrayy;
98 ✔
5578
    }
5579

5580
    /**
5581
     * Create a numerically re-indexed Arrayy object.
5582
     *
5583
     * EXAMPLE: <code>
5584
     * a([2 => 1, 3 => 2])->reindex(); // Arrayy[0 => 1, 1 => 2]
5585
     * </code>
5586
     *
5587
     * @return $this
5588
     *               <p>(Mutable) Return this Arrayy object, with re-indexed array-elements.</p>
5589
     *
5590
     * @phpstan-return static
5591
     */
5592
    public function reindex(): self
5593
    {
5594
        $this->generatorToArray(false);
63 ✔
5595

5596
        $this->array = \array_values($this->array);
63 ✔
5597

5598
        return $this;
63 ✔
5599
    }
5600

5601
    /**
5602
     * Return all items that fail the truth test.
5603
     *
5604
     * EXAMPLE: <code>
5605
     * $closure = function ($value) {
5606
     *     return $value % 2 !== 0;
5607
     * }
5608
     * a([1, 2, 3, 4])->reject($closure); // Arrayy[1 => 2, 3 => 4]
5609
     * </code>
5610
     *
5611
     * @param \Closure $closure
5612
     *
5613
     * @return static
5614
     *                <p>(Immutable)</p>
5615
     *
5616
     * @phpstan-param \Closure(T,TKey):bool  $closure
5617
     * @phpstan-return static
5618
     * @psalm-mutation-free
5619
     */
5620
    public function reject(\Closure $closure): self
5621
    {
5622
        // init
5623
        $filtered = [];
7 ✔
5624

5625
        foreach ($this->getGenerator() as $key => $value) {
7 ✔
5626
            if (!$closure($value, $key)) {
7 ✔
5627
                $filtered[$key] = $value;
7 ✔
5628
            }
5629
        }
5630

5631
        return static::create(
7 ✔
5632
            $filtered,
7 ✔
5633
            $this->iteratorClass,
7 ✔
5634
            false
7 ✔
5635
        );
7 ✔
5636
    }
5637

5638
    /**
5639
     * Remove a value from the current array (optional using dot-notation).
5640
     *
5641
     * EXAMPLE: <code>
5642
     * a([1 => 'bar', 'foo' => 'foo'])->remove(1); // Arrayy['foo' => 'foo']
5643
     * </code>
5644
     *
5645
     * @param mixed $key
5646
     *
5647
     * @return static
5648
     *                <p>(Mutable)</p>
5649
     *
5650
     * @phpstan-param  TKey|TKey[] $key
5651
     * @phpstan-return static
5652
     */
5653
    public function remove($key)
5654
    {
5655
        // recursive call
5656
        if (\is_array($key)) {
154 ✔
5657
            foreach ($key as $k) {
7 ✔
5658
                $this->internalRemove($k);
7 ✔
5659
            }
5660

5661
            return static::create(
7 ✔
5662
                $this->toArray(),
7 ✔
5663
                $this->iteratorClass,
7 ✔
5664
                false
7 ✔
5665
            );
7 ✔
5666
        }
5667

5668
        $this->internalRemove($key);
147 ✔
5669

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

5677
    /**
5678
     * alias: for "Arrayy->removeValue()"
5679
     *
5680
     * @param mixed $element
5681
     *
5682
     * @return static
5683
     *                <p>(Immutable)</p>
5684
     *
5685
     * @phpstan-param  T $element
5686
     * @phpstan-return static
5687
     * @psalm-mutation-free
5688
     */
5689
    public function removeElement($element)
5690
    {
5691
        return $this->removeValue($element);
56 ✔
5692
    }
5693

5694
    /**
5695
     * Remove the first value from the current array.
5696
     *
5697
     * EXAMPLE: <code>
5698
     * a([1 => 'bar', 'foo' => 'foo'])->removeFirst(); // Arrayy['foo' => 'foo']
5699
     * </code>
5700
     *
5701
     * @return static
5702
     *                <p>(Immutable)</p>
5703
     *
5704
     * @phpstan-return static
5705
     * @psalm-mutation-free
5706
     */
5707
    public function removeFirst(): self
5708
    {
5709
        $tmpArray = $this->toArray();
49 ✔
5710

5711
        \array_shift($tmpArray);
49 ✔
5712

5713
        return static::create(
49 ✔
5714
            $tmpArray,
49 ✔
5715
            $this->iteratorClass,
49 ✔
5716
            false
49 ✔
5717
        );
49 ✔
5718
    }
5719

5720
    /**
5721
     * Remove the last value from the current array.
5722
     *
5723
     * EXAMPLE: <code>
5724
     * a([1 => 'bar', 'foo' => 'foo'])->removeLast(); // Arrayy[1 => 'bar']
5725
     * </code>
5726
     *
5727
     * @return static
5728
     *                <p>(Immutable)</p>
5729
     *
5730
     * @phpstan-return static
5731
     * @psalm-mutation-free
5732
     */
5733
    public function removeLast(): self
5734
    {
5735
        $tmpArray = $this->toArray();
49 ✔
5736

5737
        \array_pop($tmpArray);
49 ✔
5738

5739
        return static::create(
49 ✔
5740
            $tmpArray,
49 ✔
5741
            $this->iteratorClass,
49 ✔
5742
            false
49 ✔
5743
        );
49 ✔
5744
    }
5745

5746
    /**
5747
     * Removes a particular value from an array (numeric or associative).
5748
     *
5749
     * EXAMPLE: <code>
5750
     * a([1 => 'bar', 'foo' => 'foo'])->removeValue('foo'); // Arrayy[1 => 'bar']
5751
     * </code>
5752
     *
5753
     * @param mixed $value
5754
     *
5755
     * @return static
5756
     *                <p>(Immutable)</p>
5757
     *
5758
     * @phpstan-param  T $value
5759
     * @phpstan-return static
5760
     * @psalm-mutation-free
5761
     */
5762
    public function removeValue($value): self
5763
    {
5764
        $this->generatorToArray();
56 ✔
5765

5766
        // init
5767
        $isSequentialArray = $this->isSequential();
56 ✔
5768

5769
        foreach ($this->array as $key => $item) {
56 ✔
5770
            if ($item === $value) {
49 ✔
5771
                unset($this->array[$key]);
49 ✔
5772
            }
5773
        }
5774

5775
        if ($isSequentialArray) {
56 ✔
5776
            $this->array = \array_values($this->array);
42 ✔
5777
        }
5778

5779
        return static::create(
56 ✔
5780
            $this->array,
56 ✔
5781
            $this->iteratorClass,
56 ✔
5782
            false
56 ✔
5783
        );
56 ✔
5784
    }
5785

5786
    /**
5787
     * Generate array of repeated arrays.
5788
     *
5789
     * @param int $times <p>How many times has to be repeated.</p>
5790
     *
5791
     * @return static
5792
     *                <p>(Immutable)</p>
5793
     *
5794
     * @phpstan-return static
5795
     * @psalm-mutation-free
5796
     */
5797
    public function repeat($times): self
5798
    {
5799
        if ($times === 0) {
7 ✔
5800
            return static::create([], $this->iteratorClass);
7 ✔
5801
        }
5802

5803
        return static::create(
7 ✔
5804
            \array_fill(0, (int) $times, $this->toArray()),
7 ✔
5805
            $this->iteratorClass,
7 ✔
5806
            false
7 ✔
5807
        );
7 ✔
5808
    }
5809

5810
    /**
5811
     * Replace a key with a new key/value pair.
5812
     *
5813
     * EXAMPLE: <code>
5814
     * $arrayy = a([1 => 'foo', 2 => 'foo2', 3 => 'bar']);
5815
     * $arrayy->replace(2, 'notfoo', 'notbar'); // Arrayy[1 => 'foo', 'notfoo' => 'notbar', 3 => 'bar']
5816
     * </code>
5817
     *
5818
     * @param mixed $oldKey
5819
     * @param mixed $newKey
5820
     * @param mixed $newValue
5821
     *
5822
     * @return static
5823
     *                <p>(Immutable)</p>
5824
     *
5825
     * @phpstan-param TKey $oldKey
5826
     * @phpstan-param TKey $newKey
5827
     * @phpstan-param T $newValue
5828
     * @phpstan-return static
5829
     * @psalm-mutation-free
5830
     */
5831
    public function replace($oldKey, $newKey, $newValue): self
5832
    {
5833
        $that = clone $this;
35 ✔
5834

5835
        /**
5836
         * @psalm-suppress ImpureMethodCall - object is already cloned
5837
         */
5838
        return $that->remove($oldKey)
35 ✔
5839
            ->set($newKey, $newValue);
35 ✔
5840
    }
5841

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

5879
        return static::create(
14 ✔
5880
            $data,
14 ✔
5881
            $this->iteratorClass,
14 ✔
5882
            false
14 ✔
5883
        );
14 ✔
5884
    }
5885

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

5923
        return static::create(
14 ✔
5924
            $data,
14 ✔
5925
            $this->iteratorClass,
14 ✔
5926
            false
14 ✔
5927
        );
14 ✔
5928
    }
5929

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

5955
        return static::create(
7 ✔
5956
            $result,
7 ✔
5957
            $this->iteratorClass,
7 ✔
5958
            false
7 ✔
5959
        );
7 ✔
5960
    }
5961

5962
    /**
5963
     * Replace the first matched value in an array.
5964
     *
5965
     * EXAMPLE: <code>
5966
     * $testArray = ['bar', 'foo' => 'foo', 'foobar' => 'foobar'];
5967
     * a($testArray)->replaceOneValue('foo', 'replaced'); // Arrayy['bar', 'foo' => 'replaced', 'foobar' => 'foobar']
5968
     * </code>
5969
     *
5970
     * @param mixed $search      <p>The value to replace.</p>
5971
     * @param mixed $replacement <p>The value to replace.</p>
5972
     *
5973
     * @return static
5974
     *                <p>(Immutable)</p>
5975
     *
5976
     * @phpstan-param T $search
5977
     * @phpstan-param T $replacement
5978
     * @phpstan-return static
5979
     * @psalm-mutation-free
5980
     */
5981
    public function replaceOneValue($search, $replacement = ''): self
5982
    {
5983
        $array = $this->toArray();
21 ✔
5984
        $key = \array_search($search, $array, true);
21 ✔
5985

5986
        if ($key !== false) {
21 ✔
5987
            $array[$key] = $replacement;
21 ✔
5988
        }
5989

5990
        return static::create(
21 ✔
5991
            $array,
21 ✔
5992
            $this->iteratorClass,
21 ✔
5993
            false
21 ✔
5994
        );
21 ✔
5995
    }
5996

5997
    /**
5998
     * Replace values in the current array.
5999
     *
6000
     * EXAMPLE: <code>
6001
     * $testArray = ['bar', 'foo' => 'foo', 'foobar' => 'foobar'];
6002
     * a($testArray)->replaceValues('foo', 'replaced'); // Arrayy['bar', 'foo' => 'replaced', 'foobar' => 'replacedbar']
6003
     * </code>
6004
     *
6005
     * @param string $search      <p>The value to replace.</p>
6006
     * @param string $replacement <p>What to replace it with.</p>
6007
     *
6008
     * @return static
6009
     *                <p>(Immutable)</p>
6010
     *
6011
     * @phpstan-return static
6012
     * @psalm-mutation-free
6013
     */
6014
    public function replaceValues($search, $replacement = ''): self
6015
    {
6016
        $callable = static function ($value) use ($search, $replacement) {
7 ✔
6017
            return \str_replace($search, $replacement, $value);
7 ✔
6018
        };
7 ✔
6019

6020
        /* @phpstan-ignore argument.type */
6021
        return $this->each($callable);
7 ✔
6022
    }
6023

6024
    /**
6025
     * Get the last elements from index $from until the end of this array.
6026
     *
6027
     * EXAMPLE: <code>
6028
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->rest(2); // Arrayy[0 => 'lall']
6029
     * </code>
6030
     *
6031
     * @param int $from
6032
     *
6033
     * @return static
6034
     *                <p>(Immutable)</p>
6035
     *
6036
     * @phpstan-return static
6037
     * @psalm-mutation-free
6038
     */
6039
    public function rest(int $from = 1): self
6040
    {
6041
        $tmpArray = $this->toArray();
105 ✔
6042

6043
        return static::create(
105 ✔
6044
            \array_splice($tmpArray, $from),
105 ✔
6045
            $this->iteratorClass,
105 ✔
6046
            false
105 ✔
6047
        );
105 ✔
6048
    }
6049

6050
    /**
6051
     * Return the array in the reverse order.
6052
     *
6053
     * EXAMPLE: <code>
6054
     * a([1 => 1, 2 => 2, 3 => 3])->reverse(); // self[3, 2, 1]
6055
     * </code>
6056
     *
6057
     * @return $this
6058
     *               <p>(Mutable) Return this Arrayy object.</p>
6059
     *
6060
     * @phpstan-return static
6061
     */
6062
    public function reverse(): self
6063
    {
6064
        $this->generatorToArray();
63 ✔
6065

6066
        $this->array = \array_reverse($this->array);
63 ✔
6067

6068
        return $this;
63 ✔
6069
    }
6070

6071
    /**
6072
     * Return the array with keys in the reverse order.
6073
     *
6074
     * EXAMPLE: <code>
6075
     * a([1 => 1, 2 => 2, 3 => 3])->reverse(); // self[3 => 3, 2 => 2, 1 => 1]
6076
     * </code>
6077
     *
6078
     * @return $this
6079
     *               <p>(Mutable) Return this Arrayy object.</p>
6080
     *
6081
     * @phpstan-return static
6082
     */
6083
    public function reverseKeepIndex(): self
6084
    {
6085
        $this->generatorToArray();
35 ✔
6086

6087
        $this->array = \array_reverse($this->array, true);
35 ✔
6088

6089
        return $this;
35 ✔
6090
    }
6091

6092
    /**
6093
     * Sort an array in reverse order.
6094
     *
6095
     * @param int $sort_flags [optional] <p>
6096
     *                        You may modify the behavior of the sort using the optional
6097
     *                        parameter sort_flags, for details
6098
     *                        see sort.
6099
     *                        </p>
6100
     *
6101
     * @return $this
6102
     *               <p>(Mutable) Return this Arrayy object.</p>
6103
     *
6104
     * @phpstan-return static
6105
     */
6106
    public function rsort(int $sort_flags = 0): self
6107
    {
6108
        $this->generatorToArray();
28 ✔
6109

6110
        \rsort($this->array, $sort_flags);
28 ✔
6111

6112
        return $this;
28 ✔
6113
    }
6114

6115
    /**
6116
     * Sort an array in reverse order.
6117
     *
6118
     * @param int $sort_flags [optional] <p>
6119
     *                        You may modify the behavior of the sort using the optional
6120
     *                        parameter sort_flags, for details
6121
     *                        see sort.
6122
     *                        </p>
6123
     *
6124
     * @return $this
6125
     *               <p>(Immutable) Return this Arrayy object.</p>
6126
     *
6127
     * @phpstan-return static
6128
     * @psalm-mutation-free
6129
     */
6130
    public function rsortImmutable(int $sort_flags = 0): self
6131
    {
6132
        $that = clone $this;
28 ✔
6133

6134
        /**
6135
         * @psalm-suppress ImpureMethodCall - object is already cloned
6136
         */
6137
        $that->rsort($sort_flags);
28 ✔
6138

6139
        return $that;
28 ✔
6140
    }
6141

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

6167
        return false;
77 ✔
6168
    }
6169

6170
    /**
6171
     * Search for the value of the current array via $index.
6172
     *
6173
     * EXAMPLE: <code>
6174
     * a(['fΓ²Γ΄' => 'bΓ Ε™'])->searchValue('fΓ²Γ΄'); // Arrayy[0 => 'bΓ Ε™']
6175
     * </code>
6176
     *
6177
     * @param mixed $index
6178
     *
6179
     * @return static
6180
     *                <p>(Immutable) Will return a empty Arrayy if the value wasn't found.</p>
6181
     *
6182
     * @phpstan-param TKey $index
6183
     * @phpstan-return static
6184
     * @psalm-mutation-free
6185
     */
6186
    public function searchValue($index): self
6187
    {
6188
        $this->generatorToArray();
63 ✔
6189

6190
        // init
6191
        $return = [];
63 ✔
6192

6193
        if ($this->array === []) {
63 ✔
6194
            return static::create(
×
6195
                [],
×
6196
                $this->iteratorClass,
×
6197
                false
×
6198
            );
×
6199
        }
6200

6201
        // php cast "bool"-index into "int"-index
6202
        /* @phpstan-ignore identical.alwaysFalse */
6203
        if ((bool) $index === $index) {
63 ✔
6204
            $index = (int) $index;
7 ✔
6205
        }
6206

6207
        if ($this->offsetExists($index)) {
63 ✔
6208
            $return = [$this->array[$index]];
49 ✔
6209
        }
6210

6211
        return static::create(
63 ✔
6212
            $return,
63 ✔
6213
            $this->iteratorClass,
63 ✔
6214
            false
63 ✔
6215
        );
63 ✔
6216
    }
6217

6218
    /**
6219
     * Set a value for the current array (optional using dot-notation).
6220
     *
6221
     * EXAMPLE: <code>
6222
     * $arrayy = a(['Lars' => ['lastname' => 'Moelleken']]);
6223
     * $arrayy->set('Lars.lastname', 'MΓΌller'); // Arrayy['Lars', ['lastname' => 'MΓΌller']]]
6224
     * </code>
6225
     *
6226
     * @param string $key   <p>The key to set.</p>
6227
     * @param mixed  $value <p>Its value.</p>
6228
     *
6229
     * @return $this
6230
     *               <p>(Mutable) Return this Arrayy object.</p>
6231
     *
6232
     * @phpstan-param  TKey $key
6233
     * @phpstan-param  T $value
6234
     * @phpstan-return static
6235
     */
6236
    public function set($key, $value): self
6237
    {
6238
        $this->internalSet($key, $value);
203 ✔
6239

6240
        return $this;
196 ✔
6241
    }
6242

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

6267
        // If the key doesn't exist, set it.
6268
        if (!$this->has($key)) {
77 ✔
6269
            $this->array = $this->set($key, $fallback)->toArray();
28 ✔
6270
        }
6271

6272
        return $this->get($key);
77 ✔
6273
    }
6274

6275
    /**
6276
     * Shifts a specified value off the beginning of array.
6277
     *
6278
     * @return mixed|null
6279
     *                    <p>(Mutable) A shifted element from the current array.</p>
6280
     *
6281
     * @phpstan-return T|null
6282
     */
6283
    public function shift()
6284
    {
6285
        $this->generatorToArray();
35 ✔
6286

6287
        return \array_shift($this->array);
35 ✔
6288
    }
6289

6290
    /**
6291
     * Shuffle the current array.
6292
     *
6293
     * EXAMPLE: <code>
6294
     * a([1 => 'bar', 'foo' => 'foo'])->shuffle(); // e.g.: Arrayy[['foo' => 'foo', 1 => 'bar']]
6295
     * </code>
6296
     *
6297
     * @param bool       $secure <p>using a CSPRNG | @see https://paragonie.com/b/JvICXzh_jhLyt4y3</p>
6298
     * @param array|null $array  [optional]
6299
     *
6300
     * @return static
6301
     *                <p>(Immutable)</p>
6302
     *
6303
     * @phpstan-param  array<TKey,T> $array
6304
     * @phpstan-return static
6305
     */
6306
    public function shuffle(bool $secure = false, ?array $array = null): self
6307
    {
6308
        if ($array === null) {
14 ✔
6309
            $array = $this->toArray(false);
14 ✔
6310
        }
6311

6312
        if ($secure !== true) {
14 ✔
6313
            \shuffle($array);
14 ✔
6314
        } else {
6315
            $size = \count($array, \COUNT_NORMAL);
7 ✔
6316
            $keys = \array_keys($array);
7 ✔
6317
            for ($i = $size - 1; $i > 0; --$i) {
7 ✔
6318
                try {
6319
                    $r = \random_int(0, $i);
7 ✔
6320
                } catch (\Exception $e) {
×
6321
                    /** @noinspection RandomApiMigrationInspection - "random_int" is already in use */
6322
                    $r = \mt_rand(0, $i);
×
6323
                }
6324
                if ($r !== $i) {
7 ✔
6325
                    $temp = $array[$keys[$r]];
3 ✔
6326
                    $array[$keys[$r]] = $array[$keys[$i]];
3 ✔
6327
                    $array[$keys[$i]] = $temp;
3 ✔
6328
                }
6329
            }
6330
        }
6331

6332
        foreach ($array as $key => $value) {
14 ✔
6333
            // check if recursive is needed
6334
            if (\is_array($value)) {
14 ✔
6335
                /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
6336
                /** @phpstan-var array<TKey,T> $value */
6337
                $value = $value;
×
6338

6339
                $array[$key] = $this->shuffle($secure, $value);
×
6340
            }
6341
        }
6342

6343
        return static::create(
14 ✔
6344
            $array,
14 ✔
6345
            $this->iteratorClass,
14 ✔
6346
            false
14 ✔
6347
        );
14 ✔
6348
    }
6349

6350
    /**
6351
     * Count the values from the current array.
6352
     *
6353
     * alias: for "Arrayy->count()"
6354
     *
6355
     * @param int $mode
6356
     *
6357
     * @return int
6358
     */
6359
    public function size(int $mode = \COUNT_NORMAL): int
6360
    {
6361
        return $this->count($mode);
140 ✔
6362
    }
6363

6364
    /**
6365
     * Checks whether array has exactly $size items.
6366
     *
6367
     * @param int $size
6368
     *
6369
     * @return bool
6370
     */
6371
    public function sizeIs(int $size): bool
6372
    {
6373
        // init
6374
        $itemsTempCount = 0;
7 ✔
6375

6376
        /** @noinspection PhpUnusedLocalVariableInspection */
6377
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
6378
        foreach ($this->getGeneratorByReference() as &$value) {
7 ✔
6379
            ++$itemsTempCount;
7 ✔
6380
            if ($itemsTempCount > $size) {
7 ✔
6381
                return false;
7 ✔
6382
            }
6383
        }
6384

6385
        return $itemsTempCount === $size;
7 ✔
6386
    }
6387

6388
    /**
6389
     * Checks whether array has between $fromSize to $toSize items. $toSize can be
6390
     * smaller than $fromSize.
6391
     *
6392
     * @param int $fromSize
6393
     * @param int $toSize
6394
     *
6395
     * @return bool
6396
     */
6397
    public function sizeIsBetween(int $fromSize, int $toSize): bool
6398
    {
6399
        if ($fromSize > $toSize) {
7 ✔
6400
            $tmp = $toSize;
7 ✔
6401
            $toSize = $fromSize;
7 ✔
6402
            $fromSize = $tmp;
7 ✔
6403
        }
6404

6405
        // init
6406
        $itemsTempCount = 0;
7 ✔
6407

6408
        /** @noinspection PhpUnusedLocalVariableInspection */
6409
        foreach ($this->getGenerator() as $value) {
7 ✔
6410
            ++$itemsTempCount;
7 ✔
6411
            if ($itemsTempCount > $toSize) {
7 ✔
6412
                return false;
7 ✔
6413
            }
6414
        }
6415

6416
        return $fromSize < $itemsTempCount && $itemsTempCount < $toSize;
7 ✔
6417
    }
6418

6419
    /**
6420
     * Checks whether array has more than $size items.
6421
     *
6422
     * @param int $size
6423
     *
6424
     * @return bool
6425
     */
6426
    public function sizeIsGreaterThan(int $size): bool
6427
    {
6428
        // init
6429
        $itemsTempCount = 0;
7 ✔
6430

6431
        /** @noinspection PhpUnusedLocalVariableInspection */
6432
        foreach ($this->getGenerator() as $value) {
7 ✔
6433
            ++$itemsTempCount;
7 ✔
6434
            if ($itemsTempCount > $size) {
7 ✔
6435
                return true;
7 ✔
6436
            }
6437
        }
6438

6439
        return $itemsTempCount > $size;
7 ✔
6440
    }
6441

6442
    /**
6443
     * Checks whether array has less than $size items.
6444
     *
6445
     * @param int $size
6446
     *
6447
     * @return bool
6448
     */
6449
    public function sizeIsLessThan(int $size): bool
6450
    {
6451
        // init
6452
        $itemsTempCount = 0;
7 ✔
6453

6454
        /** @noinspection PhpUnusedLocalVariableInspection */
6455
        foreach ($this->getGenerator() as $value) {
7 ✔
6456
            ++$itemsTempCount;
7 ✔
6457
            if ($itemsTempCount > $size) {
7 ✔
6458
                return false;
7 ✔
6459
            }
6460
        }
6461

6462
        return $itemsTempCount < $size;
7 ✔
6463
    }
6464

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

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

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

6552
        return $this->sorting(
140 ✔
6553
            $this->array,
140 ✔
6554
            $direction,
140 ✔
6555
            $strategy,
140 ✔
6556
            $keepKeys
140 ✔
6557
        );
140 ✔
6558
    }
6559

6560
    /**
6561
     * Sort the current array and optional you can keep the keys.
6562
     *
6563
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6564
     * @param int        $strategy  <p>sort_flags => use e.g.: <strong>SORT_REGULAR</strong> (default) or
6565
     *                              <strong>SORT_NATURAL</strong></p>
6566
     * @param bool       $keepKeys
6567
     *
6568
     * @return static
6569
     *                <p>(Immutable) Return this Arrayy object.</p>
6570
     *
6571
     * @phpstan-return static
6572
     */
6573
    public function sortImmutable(
6574
        $direction = \SORT_ASC,
6575
        int $strategy = \SORT_REGULAR,
6576
        bool $keepKeys = false
6577
    ): self {
6578
        $that = clone $this;
84 ✔
6579

6580
        $that->generatorToArray();
84 ✔
6581

6582
        return $that->sorting(
84 ✔
6583
            $that->array,
84 ✔
6584
            $direction,
84 ✔
6585
            $strategy,
84 ✔
6586
            $keepKeys
84 ✔
6587
        );
84 ✔
6588
    }
6589

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

6615
        $this->sorterKeys($this->array, $direction, $strategy);
126 ✔
6616

6617
        return $this;
126 ✔
6618
    }
6619

6620
    /**
6621
     * Sort the current array by key.
6622
     *
6623
     * @see          http://php.net/manual/en/function.ksort.php
6624
     * @see          http://php.net/manual/en/function.krsort.php
6625
     *
6626
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6627
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6628
     *                              <strong>SORT_NATURAL</strong></p>
6629
     *
6630
     * @return $this
6631
     *               <p>(Immutable) Return this Arrayy object.</p>
6632
     *
6633
     * @phpstan-return static
6634
     * @psalm-mutation-free
6635
     */
6636
    public function sortKeysImmutable(
6637
        $direction = \SORT_ASC,
6638
        int $strategy = \SORT_REGULAR
6639
    ): self {
6640
        $that = clone $this;
56 ✔
6641

6642
        /**
6643
         * @psalm-suppress ImpureMethodCall - object is already cloned
6644
         */
6645
        $that->sortKeys($direction, $strategy);
56 ✔
6646

6647
        return $that;
56 ✔
6648
    }
6649

6650
    /**
6651
     * Sort the current array by value.
6652
     *
6653
     * EXAMPLE: <code>
6654
     * a(3 => 'd', 2 => 'f', 0 => 'a')->sortValueKeepIndex(SORT_ASC, SORT_REGULAR); // Arrayy[0 => 'a', 3 => 'd', 2 => 'f']
6655
     * </code>
6656
     *
6657
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6658
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6659
     *                              <strong>SORT_NATURAL</strong></p>
6660
     *
6661
     * @return static
6662
     *                <p>(Mutable)</p>
6663
     *
6664
     * @phpstan-return static
6665
     */
6666
    public function sortValueKeepIndex(
6667
        $direction = \SORT_ASC,
6668
        int $strategy = \SORT_REGULAR
6669
    ): self {
6670
        return $this->sort($direction, $strategy, true);
7 ✔
6671
    }
6672

6673
    /**
6674
     * Sort the current array by value.
6675
     *
6676
     * EXAMPLE: <code>
6677
     * a(3 => 'd', 2 => 'f', 0 => 'a')->sortValueNewIndex(SORT_ASC, SORT_NATURAL); // Arrayy[0 => 'a', 1 => 'd', 2 => 'f']
6678
     * </code>
6679
     *
6680
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6681
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6682
     *                              <strong>SORT_NATURAL</strong></p>
6683
     *
6684
     * @return static
6685
     *                <p>(Mutable)</p>
6686
     *
6687
     * @phpstan-return static
6688
     */
6689
    public function sortValueNewIndex($direction = \SORT_ASC, int $strategy = \SORT_REGULAR): self
6690
    {
6691
        return $this->sort($direction, $strategy, false);
7 ✔
6692
    }
6693

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

6728
        // Transform all values into their results.
6729
        if ($sorter) {
7 ✔
6730
            $arrayy = static::create(
7 ✔
6731
                $array,
7 ✔
6732
                $this->iteratorClass,
7 ✔
6733
                false
7 ✔
6734
            );
7 ✔
6735

6736
            /**
6737
             * @psalm-suppress MissingClosureReturnType
6738
             * @psalm-suppress MissingClosureParamType
6739
             */
6740
            $results = $arrayy->each(
7 ✔
6741
                static function ($value) use ($sorter) {
7 ✔
6742
                    if (\is_callable($sorter) === true) {
7 ✔
6743
                        return $sorter($value);
7 ✔
6744
                    }
6745

6746
                    return $sorter === $value;
7 ✔
6747
                }
7 ✔
6748
            );
7 ✔
6749

6750
            $results = $results->toArray();
7 ✔
6751
        } else {
6752
            $results = $array;
7 ✔
6753
        }
6754

6755
        // Sort by the results and replace by original values
6756
        \array_multisort($results, $direction, $strategy, $array);
7 ✔
6757

6758
        return static::create(
7 ✔
6759
            $array,
7 ✔
6760
            $this->iteratorClass,
7 ✔
6761
            false
7 ✔
6762
        );
7 ✔
6763
    }
6764

6765
    /**
6766
     * @param int      $offset
6767
     * @param int|null $length
6768
     * @param array    $replacement
6769
     *
6770
     * @return static
6771
     *                <p>(Immutable)</p>
6772
     *
6773
     * @phpstan-param  array<T> $replacement
6774
     * @phpstan-return static
6775
     * @psalm-mutation-free
6776
     */
6777
    public function splice(int $offset, ?int $length = null, $replacement = []): self
6778
    {
6779
        $tmpArray = $this->toArray();
7 ✔
6780

6781
        \array_splice(
7 ✔
6782
            $tmpArray,
7 ✔
6783
            $offset,
7 ✔
6784
            $length ?? $this->count(),
7 ✔
6785
            $replacement
7 ✔
6786
        );
7 ✔
6787

6788
        return static::create(
7 ✔
6789
            $tmpArray,
7 ✔
6790
            $this->iteratorClass,
7 ✔
6791
            false
7 ✔
6792
        );
7 ✔
6793
    }
6794

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

6820
                    if ($i % $numberOfPieces !== 0) {
7 ✔
6821
                        ++$i;
7 ✔
6822

6823
                        continue;
7 ✔
6824
                    }
6825

6826
                    yield $carry;
7 ✔
6827

6828
                    $carry = [];
7 ✔
6829
                    $i = 1;
7 ✔
6830
                }
6831

6832
                if ($carry !== []) {
7 ✔
6833
                    yield $carry;
7 ✔
6834
                }
6835
            };
7 ✔
6836
        } else {
6837
            $generator = function () use ($numberOfPieces) {
7 ✔
6838
                $carry = [];
7 ✔
6839
                $i = 1;
7 ✔
6840
                foreach ($this->getGenerator() as $value) {
7 ✔
6841
                    $carry[] = $value;
7 ✔
6842

6843
                    if ($i % $numberOfPieces !== 0) {
7 ✔
6844
                        ++$i;
7 ✔
6845

6846
                        continue;
7 ✔
6847
                    }
6848

6849
                    yield $carry;
7 ✔
6850

6851
                    $carry = [];
7 ✔
6852
                    $i = 1;
7 ✔
6853
                }
6854

6855
                if ($carry !== []) {
7 ✔
6856
                    yield $carry;
7 ✔
6857
                }
6858
            };
7 ✔
6859
        }
6860

6861
        return static::create(
7 ✔
6862
            $generator,
7 ✔
6863
            $this->iteratorClass,
7 ✔
6864
            false
7 ✔
6865
        );
7 ✔
6866
    }
6867

6868
    /**
6869
     * Strip all empty items from the current array.
6870
     *
6871
     * EXAMPLE: <code>
6872
     * a(['a' => 1, 'b' => ''])->stripEmpty(); // Arrayy[['a' => 1]]
6873
     * </code>
6874
     *
6875
     * @return static
6876
     *                <p>(Immutable)</p>
6877
     *
6878
     * @phpstan-return static
6879
     * @psalm-mutation-free
6880
     */
6881
    public function stripEmpty(): self
6882
    {
6883
        $generator = function () {
7 ✔
6884
            foreach ($this->getGenerator() as $key => $item) {
7 ✔
6885
                if ($item === null) {
7 ✔
6886
                    continue;
7 ✔
6887
                }
6888

6889
                if ((bool) \trim((string) $item)) {
7 ✔
6890
                    yield $key => $item;
7 ✔
6891
                }
6892
            }
6893
        };
7 ✔
6894

6895
        return static::create(
7 ✔
6896
            $generator(),
7 ✔
6897
            $this->iteratorClass,
7 ✔
6898
            false
7 ✔
6899
        );
7 ✔
6900
    }
6901

6902
    /**
6903
     * Swap two values between positions by key.
6904
     *
6905
     * EXAMPLE: <code>
6906
     * a(['a' => 1, 'b' => ''])->swap('a', 'b'); // Arrayy[['a' => '', 'b' => 1]]
6907
     * </code>
6908
     *
6909
     * @param int|string $swapA <p>a key in the array</p>
6910
     * @param int|string $swapB <p>a key in the array</p>
6911
     *
6912
     * @return static
6913
     *                <p>(Immutable)</p>
6914
     *
6915
     * @phpstan-return static
6916
     * @psalm-mutation-free
6917
     */
6918
    public function swap($swapA, $swapB): self
6919
    {
6920
        $array = $this->toArray();
7 ✔
6921

6922
        list($array[$swapA], $array[$swapB]) = [$array[$swapB], $array[$swapA]];
7 ✔
6923

6924
        return static::create(
7 ✔
6925
            $array,
7 ✔
6926
            $this->iteratorClass,
7 ✔
6927
            false
7 ✔
6928
        );
7 ✔
6929
    }
6930

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

6956
            foreach ($this->getGenerator() as $key => $value) {
21 ✔
6957
                if ($value instanceof self) {
21 ✔
6958
                    $value = $value->toArray(
14 ✔
6959
                        $convertAllArrayyElements,
14 ✔
6960
                        $preserveKeys
14 ✔
6961
                    );
14 ✔
6962
                }
6963

6964
                if ($preserveKeys) {
21 ✔
6965
                    $array[$key] = $value;
14 ✔
6966
                } else {
6967
                    $array[] = $value;
7 ✔
6968
                }
6969
            }
6970

6971
            /* @phpstan-ignore return.type */
6972
            return $array;
21 ✔
6973
        }
6974

6975
        return \iterator_to_array($this->getGenerator(), $preserveKeys);
6,734 ✔
6976
    }
6977

6978
    /**
6979
     * Get the current array from the "Arrayy"-object as list.
6980
     *
6981
     * @param bool $convertAllArrayyElements <p>
6982
     *                                       Convert all Child-"Arrayy" objects also to arrays.
6983
     *                                       </p>
6984
     *
6985
     * @return array
6986
     *
6987
     * @phpstan-return list<T>
6988
     * @psalm-mutation-free
6989
     */
6990
    public function toList(bool $convertAllArrayyElements = false): array
6991
    {
6992
        /** @var list<T> - currently phpstan can't return different types depending on the phpdocs params */
6993
        return $this->toArray(
7 ✔
6994
            $convertAllArrayyElements,
7 ✔
6995
            false
7 ✔
6996
        );
7 ✔
6997
    }
6998

6999
    /**
7000
     * Convert the current array to JSON.
7001
     *
7002
     * EXAMPLE: <code>
7003
     * a(['bar', ['foo']])->toJson(); // '["bar",{"1":"foo"}]'
7004
     * </code>
7005
     *
7006
     * @param int $options [optional] <p>e.g. JSON_PRETTY_PRINT</p>
7007
     * @param int $depth   [optional] <p>Set the maximum depth. Must be greater than zero.</p>
7008
     *
7009
     * @return string
7010
     */
7011
    public function toJson(int $options = 0, int $depth = 512): string
7012
    {
7013
        if ($depth < 1) {
91 ✔
7014
            $depth = 1;
×
7015
        }
7016

7017
        $return = \json_encode($this->toArray(), $options, $depth);
91 ✔
7018
        if ($return === false) {
91 ✔
7019
            return '';
×
7020
        }
7021

7022
        return $return;
91 ✔
7023
    }
7024

7025
    /**
7026
     * @param string[]|null $items  [optional]
7027
     * @param string[]      $helper [optional]
7028
     *
7029
     * @return static|static[]
7030
     *
7031
     * @phpstan-return static
7032
     */
7033
    public function toPermutation(?array $items = null, array $helper = []): self
7034
    {
7035
        // init
7036
        $return = [];
7 ✔
7037

7038
        if ($items === null) {
7 ✔
7039
            $items = $this->toArray();
7 ✔
7040
        }
7041

7042
        if (empty($items)) {
7 ✔
7043
            $return[] = $helper;
7 ✔
7044
        } else {
7045
            for ($i = \count($items) - 1; $i >= 0; --$i) {
7 ✔
7046
                $new_items = $items;
7 ✔
7047
                $new_helper = $helper;
7 ✔
7048
                list($tmp_helper) = \array_splice($new_items, $i, 1);
7 ✔
7049
                /** @noinspection PhpSillyAssignmentInspection */
7050
                /** @var string[] $new_items */
7051
                $new_items = $new_items;
7 ✔
7052
                \array_unshift($new_helper, $tmp_helper);
7 ✔
7053
                $return = \array_merge(
7 ✔
7054
                    $return,
7 ✔
7055
                    $this->toPermutation($new_items, $new_helper)->toArray()
7 ✔
7056
                );
7 ✔
7057
            }
7058
        }
7059

7060
        /** @var static $return  - help for phpstan */
7061
        $return = static::create(
7 ✔
7062
            $return,
7 ✔
7063
            $this->iteratorClass,
7 ✔
7064
            false
7 ✔
7065
        );
7 ✔
7066

7067
        return $return;
7 ✔
7068
    }
7069

7070
    /**
7071
     * Implodes array to a string with specified separator.
7072
     *
7073
     * @param string $separator [optional] <p>The element's separator.</p>
7074
     *
7075
     * @return string
7076
     *                <p>The string representation of array, separated by ",".</p>
7077
     */
7078
    public function toString(string $separator = ','): string
7079
    {
7080
        return $this->implode($separator);
133 ✔
7081
    }
7082

7083
    /**
7084
     * Return a duplicate free copy of the current array.
7085
     *
7086
     * EXAMPLE: <code>
7087
     * a([2 => 1, 3 => 2, 4 => 2])->uniqueNewIndex(); // Arrayy[1, 2]
7088
     * </code>
7089
     *
7090
     * @return $this
7091
     *               <p>(Mutable)</p>
7092
     *
7093
     * @phpstan-return static
7094
     */
7095
    public function uniqueNewIndex(): self
7096
    {
7097
        // INFO: \array_unique() can't handle e.g. "stdClass"-values in an array
7098

7099
        $this->array = $this->reduce(
91 ✔
7100
            static function ($resultArray, $value, $key) {
91 ✔
7101
                if (!\in_array($value, $resultArray, true)) {
84 ✔
7102
                    $resultArray[] = $value;
84 ✔
7103
                }
7104

7105
                return $resultArray;
84 ✔
7106
            },
91 ✔
7107
            []
91 ✔
7108
        )->toArray();
91 ✔
7109
        $this->generator = null;
91 ✔
7110

7111
        return $this;
91 ✔
7112
    }
7113

7114
    /**
7115
     * Return a duplicate free copy of the current array. (with the old keys)
7116
     *
7117
     * EXAMPLE: <code>
7118
     * a([2 => 1, 3 => 2, 4 => 2])->uniqueNewIndex(); // Arrayy[2 => 1, 3 => 2]
7119
     * </code>
7120
     *
7121
     * @return $this
7122
     *               <p>(Mutable)</p>
7123
     *
7124
     * @phpstan-return static
7125
     */
7126
    public function uniqueKeepIndex(): self
7127
    {
7128
        // INFO: \array_unique() can't handle e.g. "stdClass"-values in an array
7129

7130
        // init
7131
        $array = $this->toArray();
77 ✔
7132

7133
        /**
7134
         * @psalm-suppress MissingClosureReturnType
7135
         * @psalm-suppress MissingClosureParamType
7136
         */
7137
        $this->array = \array_reduce(
77 ✔
7138
            \array_keys($array),
77 ✔
7139
            static function ($resultArray, $key) use ($array) {
77 ✔
7140
                if (!\in_array($array[$key], $resultArray, true)) {
70 ✔
7141
                    $resultArray[$key] = $array[$key];
70 ✔
7142
                }
7143

7144
                return $resultArray;
70 ✔
7145
            },
77 ✔
7146
            []
77 ✔
7147
        );
77 ✔
7148
        $this->generator = null;
77 ✔
7149

7150
        return $this;
77 ✔
7151
    }
7152

7153
    /**
7154
     * alias: for "Arrayy->uniqueNewIndex()"
7155
     *
7156
     * @return static
7157
     *                <p>(Mutable) Return this Arrayy object, with the appended values.</p>
7158
     *
7159
     * @see          Arrayy::unique()
7160
     *
7161
     * @phpstan-return static
7162
     */
7163
    public function unique(): self
7164
    {
7165
        return $this->uniqueNewIndex();
91 ✔
7166
    }
7167

7168
    /**
7169
     * Prepends one or more values to the beginning of array at once.
7170
     *
7171
     * @param mixed ...$args
7172
     *
7173
     * @return $this
7174
     *               <p>(Mutable) Return this Arrayy object, with prepended elements to the beginning of array.</p>
7175
     *
7176
     * @phpstan-param  array<TKey,T> ...$args
7177
     * @phpstan-return static
7178
     */
7179
    public function unshift(...$args): self
7180
    {
7181
        $this->generatorToArray();
42 ✔
7182

7183
        if (
7184
            $this->checkPropertyTypes
42 ✔
7185
            &&
7186
            $this->properties !== []
42 ✔
7187
        ) {
7188
            foreach ($args as $key => $value) {
14 ✔
7189
                $this->checkType($key, $value);
14 ✔
7190
            }
7191
        }
7192

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

7195
        return $this;
35 ✔
7196
    }
7197

7198
    /**
7199
     * Tests whether the given closure return something valid for all elements of this array.
7200
     *
7201
     * @param \Closure $closure the predicate
7202
     *
7203
     * @return bool
7204
     *              <p>TRUE, if the predicate yields TRUE for all elements, FALSE otherwise.</p>
7205
     *
7206
     * @phpstan-param \Closure(T,TKey):bool $closure
7207
     */
7208
    public function validate(\Closure $closure): bool
7209
    {
7210
        foreach ($this->getGenerator() as $key => $value) {
7 ✔
7211
            if (!$closure($value, $key)) {
7 ✔
7212
                return false;
7 ✔
7213
            }
7214
        }
7215

7216
        return true;
7 ✔
7217
    }
7218

7219
    /**
7220
     * Get all values from a array.
7221
     *
7222
     * EXAMPLE: <code>
7223
     * $arrayy = a([1 => 'foo', 2 => 'foo2', 3 => 'bar']);
7224
     * $arrayyTmp->values(); // Arrayy[0 => 'foo', 1 => 'foo2', 2 => 'bar']
7225
     * </code>
7226
     *
7227
     * @return static
7228
     *                <p>(Immutable)</p>
7229
     *
7230
     * @phpstan-return static
7231
     * @psalm-mutation-free
7232
     */
7233
    public function values(): self
7234
    {
7235
        return static::create(
14 ✔
7236
            function () {
14 ✔
7237
                foreach ($this->getGenerator() as $value) {
14 ✔
7238
                    yield $value;
14 ✔
7239
                }
7240
            },
14 ✔
7241
            $this->iteratorClass,
14 ✔
7242
            false
14 ✔
7243
        );
14 ✔
7244
    }
7245

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

7283
        if ($this->array !== []) {
84 ✔
7284
            if ($recursive === true) {
70 ✔
7285
                if ($userData !== self::ARRAYY_HELPER_WALK) {
35 ✔
7286
                    \array_walk_recursive($this->array, $callable, $userData);
×
7287
                } else {
7288
                    \array_walk_recursive($this->array, $callable);
35 ✔
7289
                }
7290
            } else {
7291
                if ($userData !== self::ARRAYY_HELPER_WALK) {
35 ✔
7292
                    \array_walk($this->array, $callable, $userData);
×
7293
                } else {
7294
                    /* @phpstan-ignore argument.type */
7295
                    \array_walk($this->array, $callable);
35 ✔
7296
                }
7297
            }
7298
        }
7299

7300
        return $this;
84 ✔
7301
    }
7302

7303
    /**
7304
     * Returns a collection of matching items.
7305
     *
7306
     * @param string $keyOrPropertyOrMethod
7307
     *                                      <p>The property or method to evaluate.</p>
7308
     * @param mixed  $value
7309
     *                                      <p>The value to match.</p>
7310
     *
7311
     * @throws \InvalidArgumentException if property or method is not defined
7312
     *
7313
     * @return static
7314
     *
7315
     * @phpstan-return static
7316
     */
7317
    public function where(string $keyOrPropertyOrMethod, $value): self
7318
    {
7319
        return $this->filter(
7 ✔
7320
            function ($item) use ($keyOrPropertyOrMethod, $value) {
7 ✔
7321
                $accessorValue = $this->extractValue(
×
7322
                    $item,
×
7323
                    $keyOrPropertyOrMethod
×
7324
                );
×
7325

7326
                return $accessorValue === $value;
×
7327
            }
7 ✔
7328
        );
7 ✔
7329
    }
7330

7331
    /**
7332
     * Convert an array into an object.
7333
     *
7334
     * @param array $array
7335
     *
7336
     * @return \stdClass
7337
     *
7338
     * @phpstan-param array<int|string,mixed> $array
7339
     */
7340
    final protected static function arrayToObject(array $array = []): \stdClass
7341
    {
7342
        // init
7343
        $object = new \stdClass();
28 ✔
7344

7345
        if (\count($array, \COUNT_NORMAL) <= 0) {
28 ✔
7346
            return $object;
7 ✔
7347
        }
7348

7349
        foreach ($array as $name => $value) {
21 ✔
7350
            if (\is_array($value)) {
21 ✔
7351
                $object->{$name} = static::arrayToObject($value);
7 ✔
7352
            } else {
7353
                $object->{$name} = $value;
21 ✔
7354
            }
7355
        }
7356

7357
        return $object;
21 ✔
7358
    }
7359

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

7392
        if ($input === null) {
77 ✔
7393
            $input = $this->getGenerator();
28 ✔
7394
        }
7395

7396
        if ($search_values === null) {
77 ✔
7397
            foreach ($input as $key => $value) {
77 ✔
7398
                $keys[] = $key;
77 ✔
7399

7400
                // check if recursive is needed
7401
                if (\is_array($value)) {
77 ✔
7402
                    $keysTmp[] = $this->array_keys_recursive($value);
28 ✔
7403
                }
7404
            }
7405
        } else {
7406
            $is_array_tmp = \is_array($search_values);
7 ✔
7407

7408
            foreach ($input as $key => $value) {
7 ✔
7409
                if (
7410
                    (
7411
                        $is_array_tmp === false
7 ✔
7412
                        &&
7 ✔
7413
                        $strict === true
7 ✔
7414
                        &&
7 ✔
7415
                        $search_values === $value
7 ✔
7416
                    )
7417
                    ||
7418
                    (
7419
                        $is_array_tmp === false
7 ✔
7420
                        &&
7 ✔
7421
                        $strict === false
7 ✔
7422
                        &&
7 ✔
7423
                        $search_values == $value
7 ✔
7424
                    )
7425
                    ||
7426
                    (
7427
                        $is_array_tmp === true
7 ✔
7428
                        &&
7 ✔
7429
                        \in_array($value, $search_values, $strict)
7 ✔
7430
                    )
7431
                ) {
7432
                    $keys[] = $key;
7 ✔
7433
                }
7434

7435
                // check if recursive is needed
7436
                if (\is_array($value)) {
7 ✔
7437
                    $keysTmp[] = $this->array_keys_recursive($value);
7 ✔
7438
                }
7439
            }
7440
        }
7441

7442
        return $keysTmp === [] ? $keys : \array_merge($keys, ...$keysTmp);
77 ✔
7443
    }
7444

7445
    /**
7446
     * @param string     $path
7447
     * @param callable   $callable
7448
     * @param array|null $currentOffset
7449
     *
7450
     * @return void
7451
     *
7452
     * @phpstan-param array<TKey,T>|null $currentOffset
7453
     * @psalm-mutation-free
7454
     */
7455
    protected function callAtPath($path, $callable, &$currentOffset = null)
7456
    {
7457
        $this->generatorToArray();
70 ✔
7458

7459
        if ($currentOffset === null) {
70 ✔
7460
            $currentOffset = &$this->array;
70 ✔
7461
        }
7462

7463
        $explodedPath = \explode($this->pathSeparator, $path);
70 ✔
7464
        /* @phpstan-ignore identical.alwaysFalse */
7465
        if ($explodedPath === false) {
70 ✔
7466
            return;
×
7467
        }
7468

7469
        $nextPath = \array_shift($explodedPath);
70 ✔
7470
        if (!isset($currentOffset[$nextPath])) {
70 ✔
7471
            return;
7 ✔
7472
        }
7473

7474
        if ($explodedPath !== []) {
63 ✔
7475
            $this->callAtPath(
7 ✔
7476
                \implode($this->pathSeparator, $explodedPath),
7 ✔
7477
                $callable,
7 ✔
7478
                $currentOffset[$nextPath]
7 ✔
7479
            );
7 ✔
7480
        } else {
7481
            $callable($currentOffset[$nextPath]);
63 ✔
7482
        }
7483
    }
7484

7485
    /**
7486
     * Extracts the value of the given property or method from the object.
7487
     *
7488
     * @param static $object
7489
     *                                         <p>The object to extract the value from.</p>
7490
     * @param string    $keyOrPropertyOrMethod
7491
     *                                         <p>The property or method for which the
7492
     *                                         value should be extracted.</p>
7493
     *
7494
     * @throws \InvalidArgumentException if the method or property is not defined
7495
     *
7496
     * @return mixed
7497
     *               <p>The value extracted from the specified property or method.</p>
7498
     *
7499
     * @phpstan-param self<TKey,T,TData> $object
7500
     */
7501
    final protected function extractValue(self $object, string $keyOrPropertyOrMethod)
7502
    {
7503
        if (isset($object[$keyOrPropertyOrMethod])) {
7 ✔
7504
            $return = $object->get($keyOrPropertyOrMethod);
7 ✔
7505

7506
            if ($return instanceof self) {
7 ✔
7507
                return $return->toArray();
×
7508
            }
7509

7510
            return $return;
7 ✔
7511
        }
7512

7513
        if (\property_exists($object, $keyOrPropertyOrMethod)) {
×
7514
            return $object->{$keyOrPropertyOrMethod};
×
7515
        }
7516

7517
        if (\method_exists($object, $keyOrPropertyOrMethod)) {
×
7518
            return $object->{$keyOrPropertyOrMethod}();
×
7519
        }
7520

7521
        throw new \InvalidArgumentException(\sprintf('array-key & property & method "%s" not defined in %s', $keyOrPropertyOrMethod, \gettype($object)));
×
7522
    }
7523

7524
    /**
7525
     * create a fallback for array
7526
     *
7527
     * 1. use the current array, if it's a array
7528
     * 2. fallback to empty array, if there is nothing
7529
     * 3. call "getArray()" on object, if there is a "Arrayy"-object
7530
     * 4. call "createFromObject()" on object, if there is a "\Traversable"-object
7531
     * 5. call "__toArray()" on object, if the method exists
7532
     * 6. cast a string or object with "__toString()" into an array
7533
     * 7. throw a "InvalidArgumentException"-Exception
7534
     *
7535
     * @param mixed $data
7536
     *
7537
     * @throws \InvalidArgumentException
7538
     *
7539
     * @return array
7540
     *
7541
     * @phpstan-return array<mixed>|array<TKey,T>
7542
     */
7543
    protected function fallbackForArray(&$data): array
7544
    {
7545
        $data = $this->internalGetArray($data);
9,119 ✔
7546

7547
        if ($data === null) {
9,119 ✔
7548
            throw new \InvalidArgumentException('Passed value should be a array');
14 ✔
7549
        }
7550

7551
        return $data;
9,105 ✔
7552
    }
7553

7554
    /**
7555
     * @param bool $preserveKeys <p>
7556
     *                           e.g.: A generator maybe return the same key more than once,
7557
     *                           so maybe you will ignore the keys.
7558
     *                           </p>
7559
     *
7560
     * @return bool
7561
     *
7562
     * @noinspection ReturnTypeCanBeDeclaredInspection
7563
     * @psalm-mutation-free :/
7564
     */
7565
    protected function generatorToArray(bool $preserveKeys = true)
7566
    {
7567
        if ($this->generator) {
8,419 ✔
7568
            $this->array = $this->toArray(false, $preserveKeys);
21 ✔
7569
            $this->generator = null;
21 ✔
7570

7571
            return true;
21 ✔
7572
        }
7573

7574
        return false;
8,419 ✔
7575
    }
7576

7577
    /**
7578
     * Get correct PHP constant for direction.
7579
     *
7580
     * @param int|string $direction
7581
     *
7582
     * @return int
7583
     * @psalm-mutation-free
7584
     */
7585
    protected function getDirection($direction): int
7586
    {
7587
        if ((string) $direction === $direction) {
301 ✔
7588
            $direction = \strtolower($direction);
70 ✔
7589

7590
            if ($direction === 'desc') {
70 ✔
7591
                $direction = \SORT_DESC;
14 ✔
7592
            } else {
7593
                $direction = \SORT_ASC;
63 ✔
7594
            }
7595
        }
7596

7597
        if (
7598
            $direction !== \SORT_DESC
301 ✔
7599
            &&
7600
            $direction !== \SORT_ASC
301 ✔
7601
        ) {
7602
            $direction = \SORT_ASC;
×
7603
        }
7604

7605
        return $direction;
301 ✔
7606
    }
7607

7608
    /**
7609
     * @return TypeCheckInterface[]
7610
     *
7611
     * @noinspection ReturnTypeCanBeDeclaredInspection
7612
     */
7613
    protected function getPropertiesFromPhpDoc()
7614
    {
7615
        static $PROPERTY_CACHE = [];
502 ✔
7616
        static $OPTIONAL_PROPERTY_CACHE = [];
502 ✔
7617
        $cacheKey = 'Class::' . static::class;
502 ✔
7618

7619
        if (isset($PROPERTY_CACHE[$cacheKey])) {
502 ✔
7620
            $this->optionalProperties = $OPTIONAL_PROPERTY_CACHE[$cacheKey] ?? [];
446 ✔
7621

7622
            return $PROPERTY_CACHE[$cacheKey];
446 ✔
7623
        }
7624

7625
        $properties = $this->getPropertiesFromNativeDefinitions();
138 ✔
7626
        $optionalProperties = [];
138 ✔
7627
        $phpDocPropertyAnnotationStyle = null;
138 ✔
7628

7629
        $reflector = new \ReflectionClass($this);
138 ✔
7630
        $factory = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
138 ✔
7631
        $docComment = $reflector->getDocComment();
138 ✔
7632
        if ($docComment) {
138 ✔
7633
            $docblock = $factory->create($docComment);
131 ✔
7634
            $this->addPropertiesFromDocBlock($docblock, $properties, $optionalProperties, $phpDocPropertyAnnotationStyle);
131 ✔
7635
        }
7636

7637
        /** @noinspection PhpAssignmentInConditionInspection */
7638
        while ($reflector = $reflector->getParentClass()) {
131 ✔
7639
            $docComment = $reflector->getDocComment();
131 ✔
7640
            if ($docComment) {
131 ✔
7641
                $docblock = $factory->create($docComment);
131 ✔
7642
                $this->addPropertiesFromDocBlock($docblock, $properties, $optionalProperties, $phpDocPropertyAnnotationStyle);
131 ✔
7643
            }
7644
        }
7645

7646
        $this->optionalProperties = $optionalProperties;
124 ✔
7647
        $OPTIONAL_PROPERTY_CACHE[$cacheKey] = $optionalProperties;
124 ✔
7648

7649
        return $PROPERTY_CACHE[$cacheKey] = $properties;
124 ✔
7650
    }
7651

7652
    /**
7653
     * Merge property definitions from a docblock into the collected property map.
7654
     *
7655
     * @param \phpDocumentor\Reflection\DocBlock $docblock
7656
     * @param TypeCheckInterface[]               $properties
7657
     * @param array<string, true>                $optionalProperties
7658
     * @param 'array-shape'|'property'|null      $phpDocPropertyAnnotationStyle
7659
     *
7660
     * @return void
7661
     */
7662
    private function addPropertiesFromDocBlock($docblock, array &$properties, array &$optionalProperties, ?string &$phpDocPropertyAnnotationStyle): void
7663
    {
7664
        $propertyTags = $docblock->getTagsByName('property');
138 ✔
7665
        $arrayShapeItems = $this->getArrayShapeItemsFromDocBlock($docblock);
138 ✔
7666

7667
        if ($propertyTags !== [] && $arrayShapeItems !== []) {
138 ✔
7668
            throw new \TypeError('Use either @property tags or array-shape annotations for Arrayy property definitions, not both.');
7 ✔
7669
        }
7670

7671
        $currentPhpDocPropertyAnnotationStyle = null;
131 ✔
7672
        if ($propertyTags !== []) {
131 ✔
7673
            $currentPhpDocPropertyAnnotationStyle = 'property';
49 ✔
7674
        } elseif ($arrayShapeItems !== []) {
131 ✔
7675
            $currentPhpDocPropertyAnnotationStyle = 'array-shape';
49 ✔
7676
        }
7677

7678
        if (
7679
            $currentPhpDocPropertyAnnotationStyle !== null
131 ✔
7680
            &&
7681
            $phpDocPropertyAnnotationStyle !== null
131 ✔
7682
            &&
7683
            $phpDocPropertyAnnotationStyle !== $currentPhpDocPropertyAnnotationStyle
131 ✔
7684
        ) {
7685
            throw new \TypeError('Use either @property tags or array-shape annotations for Arrayy property definitions, not both.');
7 ✔
7686
        }
7687

7688
        if ($currentPhpDocPropertyAnnotationStyle !== null) {
131 ✔
7689
            $phpDocPropertyAnnotationStyle = $currentPhpDocPropertyAnnotationStyle;
91 ✔
7690
        }
7691

7692
        /** @var \phpDocumentor\Reflection\DocBlock\Tags\Property $tag */
7693
        foreach ($propertyTags as $tag) {
131 ✔
7694
            $typeName = $tag->getVariableName();
42 ✔
7695
            /** @var string|null $typeName */
7696
            if (
7697
                $typeName !== null
42 ✔
7698
                &&
7699
                isset($properties[$typeName]) === false
42 ✔
7700
            ) {
7701
                $typeCheckPhpDoc = TypeCheckPhpDoc::fromPhpDocumentorProperty($tag, $typeName);
42 ✔
7702
                if ($typeCheckPhpDoc !== null) {
42 ✔
7703
                    $properties[$typeName] = $typeCheckPhpDoc;
42 ✔
7704
                    unset($optionalProperties[$typeName]);
42 ✔
7705
                }
7706
            }
7707
        }
7708

7709
        foreach ($arrayShapeItems as $item) {
131 ✔
7710
            $typeName = (string) $item->getKey();
49 ✔
7711
            if ($typeName === '') {
49 ✔
7712
                continue;
×
7713
            }
7714

7715
            $typeName = \trim($typeName, '\'"');
49 ✔
7716
            if (isset($properties[$typeName])) {
49 ✔
7717
                continue;
×
7718
            }
7719

7720
            $typeCheckPhpDoc = TypeCheckPhpDoc::fromDocTypeObject($typeName, $item->getValue());
49 ✔
7721
            $properties[$typeName] = $typeCheckPhpDoc;
49 ✔
7722
            if ($item->isOptional()) {
49 ✔
7723
                $optionalProperties[$typeName] = true;
28 ✔
7724
            }
7725
        }
7726
    }
7727

7728
    /**
7729
     * Extract array-shape items from supported @template and @extends annotations.
7730
     *
7731
     * @param \phpDocumentor\Reflection\DocBlock $docblock
7732
     *
7733
     * @return \phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem[]
7734
     */
7735
    private function getArrayShapeItemsFromDocBlock($docblock): array
7736
    {
7737
        if (!\class_exists('\phpDocumentor\Reflection\PseudoTypes\ArrayShape')) {
138 ✔
7738
            return [];
×
7739
        }
7740

7741
        $items = [];
138 ✔
7742
        foreach ($docblock->getTagsByName('template') as $tag) {
138 ✔
7743
            if (
7744
                $tag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Template
138 ✔
7745
                &&
7746
                $tag->getTemplateName() === 'T'
138 ✔
7747
                &&
7748
                $tag->getBound() instanceof \phpDocumentor\Reflection\PseudoTypes\ArrayShape
138 ✔
7749
            ) {
7750
                foreach ($tag->getBound()->getItems() as $item) {
42 ✔
7751
                    $items[] = $item;
42 ✔
7752
                }
7753
            }
7754
        }
7755

7756
        foreach ($docblock->getTagsByName('extends') as $tag) {
138 ✔
7757
            if (!$tag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Extends_) {
138 ✔
7758
                continue;
×
7759
            }
7760

7761
            $type = $tag->getType();
138 ✔
7762
            if (
7763
                !$type instanceof \phpDocumentor\Reflection\PseudoTypes\Generic
138 ✔
7764
                ||
7765
                !$this->isArrayyGenericTarget((string) $type->getFqsen())
138 ✔
7766
            ) {
7767
                continue;
124 ✔
7768
            }
7769

7770
            foreach ($type->getTypes() as $genericType) {
131 ✔
7771
                if ($genericType instanceof \phpDocumentor\Reflection\PseudoTypes\ArrayShape) {
131 ✔
7772
                    foreach ($genericType->getItems() as $item) {
14 ✔
7773
                        $items[] = $item;
14 ✔
7774
                    }
7775
                }
7776
            }
7777
        }
7778

7779
        return $items;
138 ✔
7780
    }
7781

7782
    /**
7783
     * Check whether a generic annotation target is Arrayy, ArrayyStrict, or an Arrayy subclass.
7784
     *
7785
     * @param string $fqcn
7786
     *
7787
     * @return bool
7788
     */
7789
    private function isArrayyGenericTarget(string $fqcn): bool
7790
    {
7791
        $fqcn = \ltrim($fqcn, '\\');
138 ✔
7792
        if ($fqcn === '') {
138 ✔
7793
            return false;
×
7794
        }
7795

7796
        if (\in_array($fqcn, [self::class, ArrayyStrict::class], true)) {
138 ✔
7797
            return true;
131 ✔
7798
        }
7799

7800
        return \class_exists($fqcn) && \is_a($fqcn, self::class, true);
124 ✔
7801
    }
7802

7803
    /**
7804
     * @return TypeCheckInterface[]
7805
     */
7806
    protected function getPropertiesFromNativeDefinitions(): array
7807
    {
7808
        $properties = [];
138 ✔
7809
        $reflector = new \ReflectionClass($this);
138 ✔
7810
        $reservedProperties = self::getReservedPropertyNames();
138 ✔
7811

7812
        do {
7813
            if ($reflector->getName() === self::class) {
138 ✔
7814
                break;
138 ✔
7815
            }
7816

7817
            foreach ($reflector->getProperties() as $property) {
138 ✔
7818
                if (
7819
                    $property->getDeclaringClass()->getName() !== $reflector->getName()
138 ✔
7820
                    ||
7821
                    $property->isStatic()
117 ✔
7822
                    ||
7823
                    isset($reservedProperties[$property->getName()])
117 ✔
7824
                    ||
7825
                    isset($properties[$property->getName()])
138 ✔
7826
                ) {
7827
                    continue;
138 ✔
7828
                }
7829

7830
                $properties[$property->getName()] = TypeCheckPhpDoc::fromReflectionProperty($property);
19 ✔
7831
            }
7832
        } while ($reflector = $reflector->getParentClass());
138 ✔
7833

7834
        return $properties;
138 ✔
7835
    }
7836

7837
    /**
7838
     * @return array<string, true>
7839
     */
7840
    private static function getReservedPropertyNames(): array
7841
    {
7842
        static $reservedProperties = null;
138 ✔
7843

7844
        if ($reservedProperties !== null) {
138 ✔
7845
            return $reservedProperties;
131 ✔
7846
        }
7847

7848
        $reservedProperties = [];
7 ✔
7849
        $reflector = new \ReflectionClass(self::class);
7 ✔
7850
        foreach ($reflector->getProperties() as $property) {
7 ✔
7851
            if ($property->getDeclaringClass()->getName() !== self::class) {
7 ✔
7852
                continue;
×
7853
            }
7854

7855
            $reservedProperties[$property->getName()] = true;
7 ✔
7856
        }
7857

7858
        return $reservedProperties;
7 ✔
7859
    }
7860

7861
    /**
7862
     * @param string $glue
7863
     * @param mixed  $pieces
7864
     * @param bool   $useKeys
7865
     *
7866
     * @return string
7867
     *
7868
     * @phpstan-param scalar|object|self<TKey|T>|array<TKey,T>|array<T> $pieces
7869
     * @psalm-mutation-free
7870
     */
7871
    protected function implode_recursive(
7872
        $glue = '',
7873
        $pieces = [],
7874
        bool $useKeys = false
7875
    ): string {
7876
        if ($pieces instanceof self) {
259 ✔
7877
            $pieces = $pieces->toArray();
7 ✔
7878
        }
7879

7880
        if (\is_array($pieces)) {
259 ✔
7881
            /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
7882
            /** @phpstan-var array<TKey,T> $pieces */
7883
            $pieces = $pieces;
259 ✔
7884

7885
            $pieces_count = \count($pieces, \COUNT_NORMAL);
259 ✔
7886
            $pieces_count_not_zero = $pieces_count > 0;
259 ✔
7887

7888
            return \implode(
259 ✔
7889
                $glue,
259 ✔
7890
                \array_map(
259 ✔
7891
                    [$this, 'implode_recursive'],
259 ✔
7892
                    \array_fill(0, ($pieces_count_not_zero ? $pieces_count : 1), $glue),
259 ✔
7893
                    ($useKeys === true && $pieces_count_not_zero ? $this->array_keys_recursive($pieces) : $pieces)
259 ✔
7894
                )
259 ✔
7895
            );
259 ✔
7896
        }
7897

7898
        if (
7899
            \is_scalar($pieces) === true
259 ✔
7900
            ||
7901
            $pieces instanceof \Stringable
259 ✔
7902
        ) {
7903
            return (string) $pieces;
231 ✔
7904
        }
7905

7906
        return '';
56 ✔
7907
    }
7908

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

7940
        foreach ($haystack as $item) {
127 ✔
7941
            if (\is_array($item)) {
99 ✔
7942
                $returnTmp = $this->in_array_recursive($needle, $item, $strict);
22 ✔
7943
            } else {
7944
                /** @noinspection NestedPositiveIfStatementsInspection */
7945
                if ($strict === true) {
99 ✔
7946
                    $returnTmp = $item === $needle;
99 ✔
7947
                } else {
7948
                    $returnTmp = $item == $needle;
×
7949
                }
7950
            }
7951

7952
            if ($returnTmp === true) {
99 ✔
7953
                return true;
71 ✔
7954
            }
7955
        }
7956

7957
        return false;
56 ✔
7958
    }
7959

7960
    /**
7961
     * @param mixed $data
7962
     *
7963
     * @return array<mixed>|null
7964
     */
7965
    protected function internalGetArray(&$data)
7966
    {
7967
        if (\is_array($data)) {
9,119 ✔
7968
            return $data;
9,077 ✔
7969
        }
7970

7971
        if (!$data) {
791 ✔
7972
            return [];
49 ✔
7973
        }
7974

7975
        if (\is_object($data) === true) {
784 ✔
7976
            if ($data instanceof \ArrayObject) {
735 ✔
7977
                return $data->getArrayCopy();
35 ✔
7978
            }
7979

7980
            if ($data instanceof \Generator) {
707 ✔
7981
                return static::createFromGeneratorImmutable($data)->toArray();
7 ✔
7982
            }
7983

7984
            if ($data instanceof \Traversable) {
700 ✔
7985
                return static::createFromObject($data)->toArray();
×
7986
            }
7987

7988
            if ($data instanceof \JsonSerializable) {
700 ✔
7989
                return (array) $data->jsonSerialize();
×
7990
            }
7991

7992
            if (\method_exists($data, '__toArray')) {
700 ✔
7993
                return (array) $data->__toArray();
×
7994
            }
7995

7996
            if (\method_exists($data, '__toString')) {
700 ✔
7997
                return [(string) $data];
×
7998
            }
7999
        }
8000

8001
        if (\is_callable($data)) {
749 ✔
8002
            /**
8003
             * @psalm-suppress InvalidPropertyAssignmentValue - why?
8004
             */
8005
            $this->generator = new ArrayyRewindableGenerator($data);
686 ✔
8006

8007
            return [];
686 ✔
8008
        }
8009

8010
        if (\is_scalar($data)) {
77 ✔
8011
            return [$data];
63 ✔
8012
        }
8013

8014
        return null;
14 ✔
8015
    }
8016

8017
    /**
8018
     * Internal mechanics of remove method.
8019
     *
8020
     * @param float|int|string $key
8021
     *
8022
     * @return bool
8023
     */
8024
    protected function internalRemove($key): bool
8025
    {
8026
        $this->generatorToArray();
154 ✔
8027

8028
        if (
8029
            $this->pathSeparator
154 ✔
8030
            &&
8031
            (string) $key === $key
154 ✔
8032
            &&
8033
            \strpos($key, $this->pathSeparator) !== false
154 ✔
8034
        ) {
8035
            $path = \explode($this->pathSeparator, (string) $key);
×
8036
            // crawl though the keys
8037
            while (\count($path, \COUNT_NORMAL) > 1) {
×
8038
                $key = \array_shift($path);
×
8039

8040
                if (!$this->has($key)) {
×
8041
                    return false;
×
8042
                }
8043

8044
                $this->array = &$this->array[$key];
×
8045
            }
8046

8047
            $key = \array_shift($path);
×
8048
        }
8049

8050
        unset($this->array[$key]);
154 ✔
8051

8052
        return true;
154 ✔
8053
    }
8054

8055
    /**
8056
     * Internal mechanic of set method.
8057
     *
8058
     * @param int|string|null $key
8059
     * @param mixed           $value
8060
     * @param bool            $checkProperties
8061
     *
8062
     * @return bool
8063
     *
8064
     * @phpstan-param TKey|null $key
8065
     * @phpstan-param T $value
8066
     */
8067
    protected function internalSet(
8068
        $key,
8069
        &$value,
8070
        bool $checkProperties = true
8071
    ): bool {
8072
        if (
8073
            $checkProperties === true
8,020 ✔
8074
            &&
8075
            $this->properties !== []
8,020 ✔
8076
        ) {
8077
            $this->checkType($key, $value);
1,183 ✔
8078
        }
8079

8080
        if ($key === null) {
7,978 ✔
8081
            return false;
×
8082
        }
8083

8084
        $this->generatorToArray();
7,978 ✔
8085

8086
        $array = &$this->array;
7,978 ✔
8087

8088
        /**
8089
         * https://github.com/vimeo/psalm/issues/2536
8090
         *
8091
         * @psalm-suppress PossiblyInvalidArgument
8092
         * @psalm-suppress InvalidScalarArgument
8093
         */
8094
        if (
8095
            $this->pathSeparator
7,978 ✔
8096
            &&
8097
            (string) $key === $key
7,978 ✔
8098
            &&
8099
            \strpos($key, $this->pathSeparator) !== false
7,978 ✔
8100
        ) {
8101
            $path = \explode($this->pathSeparator, (string) $key);
63 ✔
8102
            // crawl through the keys
8103
            while (\count($path, \COUNT_NORMAL) > 1) {
63 ✔
8104
                $key = \array_shift($path);
63 ✔
8105

8106
                $array = &$array[$key];
63 ✔
8107
            }
8108

8109
            $key = \array_shift($path);
63 ✔
8110
        }
8111

8112
        if ($array === null) {
7,978 ✔
8113
            $array = [];
28 ✔
8114
        } elseif (!\is_array($array)) {
7,957 ✔
8115
            throw new \RuntimeException('Can not set value at this path "' . $key . '" because (' . \gettype($array) . ')"' . \print_r($array, true) . '" is not an array.');
7 ✔
8116
        }
8117

8118
        $array[$key] = $value;
7,978 ✔
8119

8120
        return true;
7,978 ✔
8121
    }
8122

8123
    /**
8124
     * Convert a object into an array.
8125
     *
8126
     * @param mixed|object $object
8127
     *
8128
     * @return array|mixed
8129
     *
8130
     * @psalm-mutation-free
8131
     */
8132
    protected static function objectToArray($object)
8133
    {
8134
        if (!\is_object($object)) {
42 ✔
8135
            return $object;
35 ✔
8136
        }
8137

8138
        $object = \get_object_vars($object);
42 ✔
8139

8140
        /**
8141
         * @psalm-suppress PossiblyInvalidArgument - the parameter is always some kind of array - false-positive from psalm?
8142
         */
8143
        return \array_map([static::class, 'objectToArray'], $object);
42 ✔
8144
    }
8145

8146
    /**
8147
     * @param array $data
8148
     * @param bool  $checkPropertiesInConstructor
8149
     *
8150
     * @return void
8151
     *
8152
     * @phpstan-param array<mixed,T> $data
8153
     */
8154
    protected function setInitialValuesAndProperties(array &$data, bool $checkPropertiesInConstructor)
8155
    {
8156
        $checkPropertiesInConstructor = $this->checkForMissingPropertiesInConstructor === true
9,105 ✔
8157
                                        &&
9,105 ✔
8158
                                        $checkPropertiesInConstructor === true;
9,105 ✔
8159

8160
        if ($this->properties === []) {
9,105 ✔
8161
            if (
8162
                $this->checkPropertyTypes === true
8,412 ✔
8163
                ||
8164
                $checkPropertiesInConstructor === true
8,412 ✔
8165
            ) {
8166
                $this->properties = $this->getPropertiesFromPhpDoc();
481 ✔
8167
            }
8168

8169
            /** @var TypeCheckInterface[] $properties */
8170
            $properties = $this->properties;
8,398 ✔
8171
            $requiredProperties = \array_diff_key($properties, $this->optionalProperties);
8,398 ✔
8172

8173
            if (
8174
                $this->checkPropertiesMismatchInConstructor === true
8,398 ✔
8175
                &&
8176
                \count($data) !== 0
8,398 ✔
8177
                &&
8178
                \count(\array_diff_key($requiredProperties, $data)) > 0
8,398 ✔
8179
            ) {
8180
                throw new \TypeError('Property mismatch - input: ' . \print_r(\array_keys($data), true) . ' | expected: ' . \print_r(\array_keys($requiredProperties), true));
14 ✔
8181
            }
8182
        }
8183

8184
        foreach ($data as $key => &$valueInner) {
9,077 ✔
8185
            $this->internalSet(
7,894 ✔
8186
                $key,
7,894 ✔
8187
                $valueInner,
7,894 ✔
8188
                $checkPropertiesInConstructor
7,894 ✔
8189
            );
7,894 ✔
8190
        }
8191
    }
8192

8193
    /**
8194
     * sorting keys
8195
     *
8196
     * @param array      $elements
8197
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
8198
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
8199
     *                              <strong>SORT_NATURAL</strong></p>
8200
     *
8201
     * @return $this
8202
     *               <p>(Mutable) Return this Arrayy object.</p>
8203
     *
8204
     * @phpstan-param  array<mixed|TKey,T> $elements
8205
     * @phpstan-return static
8206
     */
8207
    protected function sorterKeys(
8208
        array &$elements,
8209
        $direction = \SORT_ASC,
8210
        int $strategy = \SORT_REGULAR
8211
    ): self {
8212
        $direction = $this->getDirection($direction);
126 ✔
8213

8214
        switch ($direction) {
8215
            case 'desc':
126 ✔
8216
            case \SORT_DESC:
8217
                \krsort($elements, $strategy);
42 ✔
8218

8219
                break;
42 ✔
8220
            case 'asc':
91 ✔
8221
            case \SORT_ASC:
91 ✔
8222
            default:
8223
                \ksort($elements, $strategy);
91 ✔
8224
        }
8225

8226
        return $this;
126 ✔
8227
    }
8228

8229
    /**
8230
     * @param array      $elements  <p>Warning: used as reference</p>
8231
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
8232
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
8233
     *                              <strong>SORT_NATURAL</strong></p>
8234
     * @param bool       $keepKeys
8235
     *
8236
     * @return $this
8237
     *               <p>(Mutable) Return this Arrayy object.</p>
8238
     *
8239
     * @phpstan-param array<mixed|TKey,T> $elements
8240
     * @phpstan-return static
8241
     */
8242
    protected function sorting(
8243
        array &$elements,
8244
        $direction = \SORT_ASC,
8245
        int $strategy = \SORT_REGULAR,
8246
        bool $keepKeys = false
8247
    ): self {
8248
        $direction = $this->getDirection($direction);
168 ✔
8249

8250
        if (!$strategy) {
168 ✔
8251
            $strategy = \SORT_REGULAR;
168 ✔
8252
        }
8253

8254
        switch ($direction) {
8255
            case 'desc':
168 ✔
8256
            case \SORT_DESC:
8257
                if ($keepKeys) {
91 ✔
8258
                    \arsort($elements, $strategy);
63 ✔
8259
                } else {
8260
                    \rsort($elements, $strategy);
28 ✔
8261
                }
8262

8263
                break;
91 ✔
8264
            case 'asc':
77 ✔
8265
            case \SORT_ASC:
77 ✔
8266
            default:
8267
                if ($keepKeys) {
77 ✔
8268
                    \asort($elements, $strategy);
28 ✔
8269
                } else {
8270
                    \sort($elements, $strategy);
49 ✔
8271
                }
8272
        }
8273

8274
        return $this;
168 ✔
8275
    }
8276

8277
    /**
8278
     * @param array $array
8279
     *
8280
     * @return array
8281
     *
8282
     * @phpstan-param array<array-key, mixed> $array
8283
     * @phpstan-return array<array-key, mixed>
8284
     *
8285
     * @psalm-mutation-free
8286
     */
8287
    private function getArrayRecursiveHelperArrayy(array $array)
8288
    {
8289
        if ($array === []) {
175 ✔
8290
            return [];
×
8291
        }
8292

8293
        \array_walk_recursive(
175 ✔
8294
            $array,
175 ✔
8295
            /**
8296
             * @param array|self $item
8297
             *
8298
             * @return void
8299
             */
8300
            static function (&$item) {
175 ✔
8301
                if ($item instanceof self) {
175 ✔
8302
                    $item = $item->getArray();
7 ✔
8303
                }
8304
            }
175 ✔
8305
        );
175 ✔
8306

8307
        return $array;
175 ✔
8308
    }
8309

8310
    /**
8311
     * @param int|string|null $key
8312
     * @param mixed           $value
8313
     *
8314
     * @return void
8315
     */
8316
    private function checkType($key, $value)
8317
    {
8318
        if (
8319
            $key !== null
1,183 ✔
8320
            &&
8321
            isset($this->properties[$key]) === false
1,183 ✔
8322
            &&
8323
            $this->checkPropertiesMismatch === true
1,183 ✔
8324
        ) {
8325
            throw new \TypeError('The key "' . $key . '" does not exist as a property definition. (' . \get_class($this) . ').');
28 ✔
8326
        }
8327

8328
        if (isset($this->properties[self::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES])) {
1,183 ✔
8329
            $this->properties[self::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES]->checkType($value);
833 ✔
8330
        } elseif ($key !== null && isset($this->properties[$key])) {
427 ✔
8331
            $this->properties[$key]->checkType($value);
427 ✔
8332
        }
8333
    }
8334
}
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