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

voku / Arrayy / 30194830563

26 Jul 2026 08:33AM UTC coverage: 91.157% (-1.2%) from 92.38%
30194830563

Pull #175

github

web-flow
Merge e991bc12f into 57c7fca16
Pull Request #175: Fix PHPStan blind spots and runtime edge cases in Arrayy; add audit and tests

24 of 78 new or added lines in 4 files covered. (30.77%)

1 existing line in 1 file now uncovered.

2773 of 3042 relevant lines covered (91.16%)

246.83 hits per line

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

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

3
declare(strict_types=1);
4

5
namespace Arrayy;
6

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

176
        return $return;
×
177
    }
178

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

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

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

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

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

248
        if (\is_array($return) === true) {
1,181 ✔
249
            $return = static::create(
×
250
                /* @phpstan-ignore-next-line argument.type */
251
                [],
×
252
                $this->iteratorClass,
×
253
                false
×
254
            )->createByReference($return);
×
255
        }
256

257
        return $return;
1,181 ✔
258
    }
259

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

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

289
            return $this;
28 ✔
290
        }
291

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

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

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

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

335
        return $this;
140 ✔
336
    }
337

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

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

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

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

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

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

405
        return $this;
28 ✔
406
    }
407

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

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

432
        return $that;
28 ✔
433
    }
434

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

575
        return $return;
×
576
    }
577

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

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

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

611
        return $this;
28 ✔
612
    }
613

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

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

637
        return $that;
28 ✔
638
    }
639

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

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

655
        return $this;
56 ✔
656
    }
657

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

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

676
        return $that;
28 ✔
677
    }
678

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

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

694
        return $this;
70 ✔
695
    }
696

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

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

715
        return $that;
28 ✔
716
    }
717

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

736
        $offsetExists = $this->keyExists($offset);
1,398 ✔
737
        if ($offsetExists === true) {
1,398 ✔
738
            return true;
1,244 ✔
739
        }
740

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

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

771
        return $offsetExists;
987 ✔
772
    }
773

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

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

797
        /* @phpstan-ignore-next-line return.type */
798
        return $value;
1,174 ✔
799
    }
800

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

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

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

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

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

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

849
            return;
98 ✔
850
        }
851

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

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

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

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

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

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

920
            return;
8,979 ✔
921
        }
922

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

932
                return;
×
933
            }
934
        }
935

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

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

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

959
        return $this;
56 ✔
960
    }
961

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

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

985
        return $that;
28 ✔
986
    }
987

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

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

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

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

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

1088
        return $this;
7 ✔
1089
    }
1090

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

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

1120
        return self::create(
70 ✔
1121
            /* @phpstan-ignore-next-line argument.type */
1122
            $result,
70 ✔
1123
            $this->iteratorClass,
70 ✔
1124
            false
70 ✔
1125
        );
70 ✔
1126
    }
1127

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

1144
        foreach ($this->getGenerator() as $key => $item) {
70 ✔
1145
            if ($item instanceof self) {
63 ✔
1146
                $result[$key] = $item->appendToEachValue($prefix);
×
1147
            } elseif (\is_array($item)) {
63 ✔
1148
                /* @phpstan-ignore-next-line argument.type */
UNCOV
1149
                $result[$key] = self::create($item, $this->iteratorClass, false)->appendToEachValue($prefix)->toArray();
×
1150
            } elseif (\is_object($item) === true) {
63 ✔
1151
                $result[$key] = $item;
7 ✔
1152
            } else {
1153
                $result[$key] = $prefix . $item;
56 ✔
1154
            }
1155
        }
1156

1157
        /* @phpstan-ignore-next-line argument.type */
1158
        return self::create($result, $this->iteratorClass, false);
70 ✔
1159
    }
1160

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

1173
        \arsort($this->array);
28 ✔
1174

1175
        return $this;
28 ✔
1176
    }
1177

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

1191
        $that->generatorToArray();
70 ✔
1192

1193
        \arsort($that->array);
70 ✔
1194

1195
        return $that;
70 ✔
1196
    }
1197

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

1222
        foreach ($that->getGenerator() as $key => $value) {
21 ✔
1223
            $closure($value, $key);
21 ✔
1224
        }
1225

1226
        return static::create(
21 ✔
1227
            /* @phpstan-ignore-next-line argument.type */
1228
            $that->toArray(),
21 ✔
1229
            $this->iteratorClass,
21 ✔
1230
            false
21 ✔
1231
        );
21 ✔
1232
    }
1233

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

1252
        if (!$count) {
70 ✔
1253
            return 0;
14 ✔
1254
        }
1255

1256
        if ((int) $decimals !== $decimals) {
56 ✔
1257
            $decimals = 0;
21 ✔
1258
        }
1259

1260
        $sum = 0;
56 ✔
1261
        foreach ($array as $value) {
56 ✔
1262
            if (
1263
                \is_int($value)
56 ✔
1264
                ||
1265
                \is_float($value)
49 ✔
1266
                ||
1267
                \is_bool($value)
56 ✔
1268
            ) {
1269
                $sum += $value;
42 ✔
1270
            } elseif (\is_string($value) && \is_numeric($value)) {
14 ✔
1271
                $sum += (float) $value;
×
1272
            }
1273
        }
1274

1275
        return \round($sum / $count, $decimals);
56 ✔
1276
    }
1277

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

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

1316
            $return[$key] = $value;
7 ✔
1317
        }
1318

1319
        return static::create(
7 ✔
1320
            /* @phpstan-ignore-next-line argument.type */
1321
            $return,
7 ✔
1322
            $this->iteratorClass,
7 ✔
1323
            false
7 ✔
1324
        );
7 ✔
1325
    }
1326

1327
    /**
1328
     * Change the path separator of the array wrapper.
1329
     *
1330
     * By default, the separator is: "."
1331
     *
1332
     * @param non-empty-string $separator <p>Separator to set.</p>
1333
     *
1334
     * @return $this
1335
     *               <p>(Mutable) Return this Arrayy object.</p>
1336
     *
1337
     * @phpstan-return static
1338
     */
1339
    public function changeSeparator($separator): self
1340
    {
1341
        $this->pathSeparator = $separator;
84 ✔
1342

1343
        return $this;
84 ✔
1344
    }
1345

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

1371
                    $values[$key] = $value;
×
1372
                    if ($tmpCounter === $size) {
×
1373
                        yield $values;
×
1374

1375
                        $values = [];
×
1376
                        $tmpCounter = 0;
×
1377
                    }
1378
                }
1379

1380
                if ($values !== []) {
×
1381
                    yield $values;
×
1382
                }
1383
            };
×
1384
        } else {
1385
            $generator = function () use ($size) {
42 ✔
1386
                $values = [];
42 ✔
1387
                $tmpCounter = 0;
42 ✔
1388
                foreach ($this->getGenerator() as $value) {
42 ✔
1389
                    ++$tmpCounter;
42 ✔
1390

1391
                    $values[] = $value;
42 ✔
1392
                    if ($tmpCounter === $size) {
42 ✔
1393
                        yield $values;
42 ✔
1394

1395
                        $values = [];
42 ✔
1396
                        $tmpCounter = 0;
42 ✔
1397
                    }
1398
                }
1399

1400
                if ($values !== []) {
42 ✔
1401
                    yield $values;
35 ✔
1402
                }
1403
            };
42 ✔
1404
        }
1405

1406
        return static::create(
42 ✔
1407
            $generator,
42 ✔
1408
            $this->iteratorClass,
42 ✔
1409
            false
42 ✔
1410
        );
42 ✔
1411
    }
1412

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

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

1460
            return $this;
21 ✔
1461
        }
1462

1463
        $this->array = [];
49 ✔
1464
        $this->generator = null;
49 ✔
1465

1466
        return $this;
49 ✔
1467
    }
1468

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

1489
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1490
        $tmpCount = 0;
70 ✔
1491
        foreach ($this->getGeneratorByReference() as &$valueFromArray) {
70 ✔
1492
            $tmpCount++;
56 ✔
1493

1494
            if ($strict) {
56 ✔
1495
                if ($value !== $valueFromArray) {
56 ✔
1496
                    return false;
36 ✔
1497
                }
1498
            } else {
1499
                /** @noinspection NestedPositiveIfStatementsInspection */
1500
                if ($value != $valueFromArray) {
×
1501
                    return false;
×
1502
                }
1503
            }
1504
        }
1505

1506
        return $tmpCount !== 0;
49 ✔
1507
    }
1508

1509
    /**
1510
     * Check if an item is in the current array.
1511
     *
1512
     * EXAMPLE: <code>
1513
     * a([1, true])->contains(true); // true
1514
     * </code>
1515
     *
1516
     * @param float|int|string $value
1517
     * @param bool             $recursive
1518
     * @param bool             $strict
1519
     *
1520
     * @return bool
1521
     * @psalm-mutation-free
1522
     */
1523
    public function contains($value, bool $recursive = false, bool $strict = true): bool
1524
    {
1525
        if ($recursive === true) {
165 ✔
1526
            return $this->in_array_recursive($value, $this->toArray(), $strict);
130 ✔
1527
        }
1528

1529
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1530
        foreach ($this->getGeneratorByReference() as &$valueFromArray) {
98 ✔
1531
            if ($strict) {
77 ✔
1532
                if ($value === $valueFromArray) {
77 ✔
1533
                    return true;
65 ✔
1534
                }
1535
            } else {
1536
                /** @noinspection NestedPositiveIfStatementsInspection */
1537
                if ($value == $valueFromArray) {
×
1538
                    return true;
×
1539
                }
1540
            }
1541
        }
1542

1543
        return false;
49 ✔
1544
    }
1545

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

1567
        if ($recursive === true) {
168 ✔
1568
            /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1569
            foreach ($this->getGeneratorByReference() as &$valueTmp) {
168 ✔
1570
                if (\is_array($valueTmp)) {
154 ✔
1571
                    $return = (new self($valueTmp))->containsCaseInsensitive($value, $recursive);
35 ✔
1572
                    if ($return === true) {
35 ✔
1573
                        return $return;
27 ✔
1574
                    }
1575
                } elseif (\mb_strtoupper((string) $valueTmp) === \mb_strtoupper((string) $value)) {
154 ✔
1576
                    return true;
112 ✔
1577
                }
1578
            }
1579

1580
            return false;
56 ✔
1581
        }
1582

1583
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1584
        foreach ($this->getGeneratorByReference() as &$valueTmp) {
84 ✔
1585
            if (\mb_strtoupper((string) $valueTmp) === \mb_strtoupper((string) $value)) {
77 ✔
1586
                return true;
56 ✔
1587
            }
1588
        }
1589

1590
        return false;
28 ✔
1591
    }
1592

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

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

1646
        return \count(
7 ✔
1647
            \array_intersect($needles, $this->keys()->toArray()),
7 ✔
1648
            \COUNT_NORMAL
7 ✔
1649
        )
7 ✔
1650
                ===
7 ✔
1651
                \count(
7 ✔
1652
                    $needles,
7 ✔
1653
                    \COUNT_NORMAL
7 ✔
1654
                );
7 ✔
1655
    }
1656

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

1673
    /**
1674
     * alias: for "Arrayy->contains()"
1675
     *
1676
     * @param float|int|string $value
1677
     *
1678
     * @return bool
1679
     *
1680
     * @see Arrayy::contains()
1681
     * @psalm-mutation-free
1682
     */
1683
    public function containsValue($value): bool
1684
    {
1685
        return $this->contains($value);
63 ✔
1686
    }
1687

1688
    /**
1689
     * alias: for "Arrayy->contains($value, true)"
1690
     *
1691
     * @param float|int|string $value
1692
     *
1693
     * @return bool
1694
     *
1695
     * @see Arrayy::contains()
1696
     * @psalm-mutation-free
1697
     */
1698
    public function containsValueRecursive($value): bool
1699
    {
1700
        return $this->contains($value, true);
126 ✔
1701
    }
1702

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

1734
    /**
1735
     * Counts all the values of an array
1736
     *
1737
     * @see          http://php.net/manual/en/function.array-count-values.php
1738
     *
1739
     * @return static
1740
     *                <p>
1741
     *                (Immutable)
1742
     *                An associative Arrayy-object of values from input as
1743
     *                keys and their count as value.
1744
     *                </p>
1745
     *
1746
     * @phpstan-return static
1747
     * @psalm-mutation-free
1748
     */
1749
    public function countValues(): self
1750
    {
1751
        /** @phpstan-var static $return - help for phpstan */
1752
        /* @phpstan-ignore-next-line argument.type */
1753
        $return = self::create(\array_count_values($this->toArray()), $this->iteratorClass);
49 ✔
1754

1755
        return $return;
49 ✔
1756
    }
1757

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

1785
        return $instance;
5,499 ✔
1786
    }
1787

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

1812
        if ($items === null) {
14 ✔
1813
            $items = $this->getArray();
14 ✔
1814
        }
1815

1816
        foreach ($items as $key => $value) {
14 ✔
1817
            if (\is_array($value) && $value !== []) {
14 ✔
1818
                $flatten[] = $this->flatten($delimiter, $prepend . $key . $delimiter, $value);
14 ✔
1819
            } else {
1820
                $flatten[] = [$prepend . $key => $value];
14 ✔
1821
            }
1822
        }
1823

1824
        if (\count($flatten) === 0) {
14 ✔
1825
            return [];
×
1826
        }
1827

1828
        return \array_merge_recursive([], ...$flatten);
14 ✔
1829
    }
1830

1831
    /**
1832
     * WARNING: Creates an Arrayy object by reference.
1833
     *
1834
     * @param array $array
1835
     *
1836
     * @return $this
1837
     *               <p>(Mutable) Return this Arrayy object.</p>
1838
     *
1839
     * @phpstan-param  array<TKey,T> $array
1840
     * @phpstan-return $this
1841
     *
1842
     * @internal this will not check any types because it's set directly as reference
1843
     */
1844
    public function createByReference(array &$array = []): self
1845
    {
1846
        $this->array = &$array;
200 ✔
1847
        $this->generator = null;
200 ✔
1848

1849
        return $this;
200 ✔
1850
    }
1851

1852
    /**
1853
     * Create an new instance from a callable function which will return an Generator.
1854
     *
1855
     * @param callable $generatorFunction
1856
     *
1857
     * @return static
1858
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1859
     *
1860
     * @phpstan-param callable():\Generator<TKey,T> $generatorFunction
1861
     * @phpstan-return static
1862
     * @psalm-mutation-free
1863
     */
1864
    public static function createFromGeneratorFunction(callable $generatorFunction): self
1865
    {
1866
        return self::create($generatorFunction);
56 ✔
1867
    }
1868

1869
    /**
1870
     * Create an new instance filled with a copy of values from a "Generator"-object.
1871
     *
1872
     * @param \Generator $generator
1873
     *
1874
     * @return static
1875
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1876
     *
1877
     * @phpstan-param \Generator<TKey,T> $generator
1878
     * @phpstan-return static
1879
     * @psalm-mutation-free
1880
     */
1881
    public static function createFromGeneratorImmutable(\Generator $generator): self
1882
    {
1883
        /* @phpstan-ignore-next-line argument.type */
1884
        return self::create(\iterator_to_array($generator, true));
35 ✔
1885
    }
1886

1887
    /**
1888
     * Create an new Arrayy object via JSON.
1889
     *
1890
     * @param string $json
1891
     *
1892
     * @return static
1893
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1894
     *
1895
     * @phpstan-return static
1896
     * @psalm-mutation-free
1897
     */
1898
    public static function createFromJson(string $json): self
1899
    {
1900
        return static::create(\json_decode($json, true));
42 ✔
1901
    }
1902

1903
    /**
1904
     * Create an new Arrayy object via JSON.
1905
     *
1906
     * @param array $array
1907
     *
1908
     * @return static
1909
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1910
     *
1911
     * @phpstan-param array<TKey,T> $array
1912
     * @phpstan-return static
1913
     * @psalm-mutation-free
1914
     */
1915
    public static function createFromArray(array $array): self
1916
    {
1917
        /* @phpstan-ignore-next-line argument.type */
1918
        return static::create($array);
7 ✔
1919
    }
1920

1921
    /**
1922
     * Create an new instance filled with values from an object that is iterable.
1923
     *
1924
     * @param \Traversable $object <p>iterable object</p>
1925
     *
1926
     * @return static
1927
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1928
     *
1929
     * @phpstan-param \Traversable<array-key,T> $object
1930
     * @phpstan-return static
1931
     * @psalm-mutation-free
1932
     */
1933
    public static function createFromObject(\Traversable $object): self
1934
    {
1935
        // init
1936
        $arrayy = static::create();
28 ✔
1937

1938
        if ($object instanceof self) {
28 ✔
1939
            $objectArray = $object->getGenerator();
28 ✔
1940
        } else {
1941
            $objectArray = $object;
×
1942
        }
1943

1944
        foreach ($objectArray as $key => $value) {
28 ✔
1945
            /**
1946
             * @psalm-suppress ImpureMethodCall - object is already re-created
1947
             */
1948
            $arrayy->internalSet($key, $value);
21 ✔
1949
        }
1950

1951
        return $arrayy;
28 ✔
1952
    }
1953

1954
    /**
1955
     * Create an new instance filled with values from an object.
1956
     *
1957
     * @param object $object
1958
     *
1959
     * @return static
1960
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1961
     *
1962
     * @phpstan-return static
1963
     * @psalm-mutation-free
1964
     */
1965
    public static function createFromObjectVars($object): self
1966
    {
1967
        return self::create(self::objectToArray($object));
42 ✔
1968
    }
1969

1970
    /**
1971
     * Create an new Arrayy object via string.
1972
     *
1973
     * @param string                $str       <p>The input string.</p>
1974
     * @param non-empty-string|null $delimiter <p>The boundary string.</p>
1975
     * @param string|null           $regEx     <p>Use the $delimiter or the $regEx, so if $pattern is null, $delimiter will be
1976
     *                                         used.</p>
1977
     *
1978
     * @return static
1979
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1980
     *
1981
     * @phpstan-return static
1982
     * @psalm-mutation-free
1983
     */
1984
    public static function createFromString(string $str, ?string $delimiter = null, ?string $regEx = null): self
1985
    {
1986
        if ($regEx) {
70 ✔
1987
            \preg_match_all($regEx, $str, $array);
7 ✔
1988

1989
            if (!empty($array)) {
7 ✔
1990
                $array = $array[0];
7 ✔
1991
            }
1992
        } else {
1993
            /** @noinspection NestedPositiveIfStatementsInspection */
1994
            if ($delimiter !== null) {
63 ✔
1995
                $array = \explode($delimiter, $str);
49 ✔
1996
            } else {
1997
                $array = [$str];
14 ✔
1998
            }
1999
        }
2000

2001
        // trim all string in the array
2002
        /**
2003
         * @psalm-suppress MissingClosureParamType
2004
         */
2005
        \array_walk(
70 ✔
2006
            $array,
70 ✔
2007
            static function (&$val) {
70 ✔
2008
                if ((string) $val === $val) {
70 ✔
2009
                    $val = \trim($val);
70 ✔
2010
                }
2011
            }
70 ✔
2012
        );
70 ✔
2013

2014
        /** @var static $return - help for phpstan */
2015
        /* @phpstan-ignore-next-line argument.type */
2016
        $return = static::create($array);
70 ✔
2017

2018
        return $return;
70 ✔
2019
    }
2020

2021
    /**
2022
     * Create an new instance filled with a copy of values from a "Traversable"-object.
2023
     *
2024
     * @param \Traversable $traversable
2025
     * @param bool         $use_keys    [optional] <p>
2026
     *                                  Whether to use the iterator element keys as index.
2027
     *                                  </p>
2028
     *
2029
     * @return static
2030
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
2031
     *
2032
     * @phpstan-param \Traversable<array-key|TKey,T> $traversable
2033
     * @phpstan-return static
2034
     * @psalm-mutation-free
2035
     */
2036
    public static function createFromTraversableImmutable(\Traversable $traversable, bool $use_keys = true): self
2037
    {
2038
        /* @phpstan-ignore-next-line argument.type */
2039
        return self::create(\iterator_to_array($traversable, $use_keys));
7 ✔
2040
    }
2041

2042
    /**
2043
     * Create an new instance containing a range of elements.
2044
     *
2045
     * @param float|int|string $low  <p>First value of the sequence.</p>
2046
     * @param float|int|string $high <p>The sequence is ended upon reaching the end value.</p>
2047
     * @param float|int        $step <p>Used as the increment between elements in the sequence.</p>
2048
     *
2049
     * @return static
2050
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
2051
     *
2052
     * @phpstan-return static
2053
     * @psalm-mutation-free
2054
     */
2055
    public static function createWithRange($low, $high, $step = 1): self
2056
    {
2057
        /** @phpstan-var static $return - help for phpstan */
2058
        /* @phpstan-ignore-next-line argument.type */
2059
        $return = static::create(\range($low, $high, $step));
14 ✔
2060

2061
        return $return;
14 ✔
2062
    }
2063

2064
    /**
2065
     * Gets the element of the array at the current internal iterator position.
2066
     *
2067
     * @return false|mixed
2068
     *
2069
     * @phpstan-return false|T
2070
     */
2071
    public function current()
2072
    {
2073
        if ($this->generator) {
×
2074
            return $this->generator->current();
×
2075
        }
2076

2077
        return \current($this->array);
×
2078
    }
2079

2080
    /**
2081
     * Custom sort by index via "uksort".
2082
     *
2083
     * EXAMPLE: <code>
2084
     * $callable = function ($a, $b) {
2085
     *     if ($a == $b) {
2086
     *         return 0;
2087
     *     }
2088
     *     return ($a > $b) ? 1 : -1;
2089
     * };
2090
     * $arrayy = a(['three' => 3, 'one' => 1, 'two' => 2]);
2091
     * $resultArrayy = $arrayy->customSortKeys($callable); // Arrayy['one' => 1, 'three' => 3, 'two' => 2]
2092
     * </code>
2093
     *
2094
     * @see          http://php.net/manual/en/function.uksort.php
2095
     *
2096
     * @param callable $callable
2097
     *
2098
     * @throws \InvalidArgumentException
2099
     *
2100
     * @return $this
2101
     *               <p>(Mutable) Return this Arrayy object.</p>
2102
     *
2103
     * @phpstan-param  callable(TKey,TKey):int $callable
2104
     * @phpstan-return static
2105
     */
2106
    public function customSortKeys(callable $callable): self
2107
    {
2108
        $this->generatorToArray();
35 ✔
2109

2110
        \uksort($this->array, $callable);
35 ✔
2111

2112
        return $this;
35 ✔
2113
    }
2114

2115
    /**
2116
     * Custom sort by index via "uksort".
2117
     *
2118
     * @see          http://php.net/manual/en/function.uksort.php
2119
     *
2120
     * @param callable $callable
2121
     *
2122
     * @throws \InvalidArgumentException
2123
     *
2124
     * @return $this
2125
     *               <p>(Immutable) Return this Arrayy object.</p>
2126
     *
2127
     * @phpstan-param  callable(TKey,TKey):int $callable
2128
     * @phpstan-return static
2129
     * @psalm-mutation-free
2130
     */
2131
    public function customSortKeysImmutable(callable $callable): self
2132
    {
2133
        $that = clone $this;
7 ✔
2134

2135
        $that->generatorToArray();
7 ✔
2136

2137
        /**
2138
         * @psalm-suppress ImpureFunctionCall - object is already cloned
2139
         */
2140
        \uksort($that->array, $callable);
7 ✔
2141

2142
        return $that;
7 ✔
2143
    }
2144

2145
    /**
2146
     * Custom sort by value via "usort".
2147
     *
2148
     * EXAMPLE: <code>
2149
     * $callable = function ($a, $b) {
2150
     *     if ($a == $b) {
2151
     *         return 0;
2152
     *     }
2153
     *     return ($a > $b) ? 1 : -1;
2154
     * };
2155
     * $arrayy = a(['three' => 3, 'one' => 1, 'two' => 2]);
2156
     * $resultArrayy = $arrayy->customSortValues($callable); // Arrayy[1, 2, 3]
2157
     * </code>
2158
     *
2159
     * @see          http://php.net/manual/en/function.usort.php
2160
     *
2161
     * @param callable $callable
2162
     *
2163
     * @return $this
2164
     *               <p>(Mutable) Return this Arrayy object.</p>
2165
     *
2166
     * @phpstan-param  callable(T,T):int $callable
2167
     * @phpstan-return static
2168
     */
2169
    public function customSortValues(callable $callable): self
2170
    {
2171
        $this->generatorToArray();
77 ✔
2172

2173
        \usort($this->array, $callable);
77 ✔
2174

2175
        return $this;
77 ✔
2176
    }
2177

2178
    /**
2179
     * Custom sort by value via "usort".
2180
     *
2181
     * @see          http://php.net/manual/en/function.usort.php
2182
     *
2183
     * @param callable $callable
2184
     *
2185
     * @throws \InvalidArgumentException
2186
     *
2187
     * @return $this
2188
     *               <p>(Immutable) Return this Arrayy object.</p>
2189
     *
2190
     * @phpstan-param  callable(T,T):int $callable
2191
     * @phpstan-return static
2192
     * @psalm-mutation-free
2193
     */
2194
    public function customSortValuesImmutable($callable): self
2195
    {
2196
        $that = clone $this;
35 ✔
2197

2198
        /**
2199
         * @psalm-suppress ImpureMethodCall - object is already cloned
2200
         */
2201
        $that->customSortValues($callable);
35 ✔
2202

2203
        return $that;
35 ✔
2204
    }
2205

2206
    /**
2207
     * Delete the given key or keys.
2208
     *
2209
     * @param int|int[]|string|string[] $keyOrKeys
2210
     *
2211
     * @return void
2212
     */
2213
    public function delete($keyOrKeys)
2214
    {
2215
        $keyOrKeys = (array) $keyOrKeys;
63 ✔
2216

2217
        foreach ($keyOrKeys as $key) {
63 ✔
2218
            $this->offsetUnset($key);
63 ✔
2219
        }
2220
    }
2221

2222
    /**
2223
     * Return elements where the values that are only in the current array.
2224
     *
2225
     * EXAMPLE: <code>
2226
     * a([1 => 1, 2 => 2])->diff([1 => 1]); // Arrayy[2 => 2]
2227
     * </code>
2228
     *
2229
     * @param array ...$array
2230
     *
2231
     * @return static
2232
     *                <p>(Immutable)</p>
2233
     *
2234
     * @phpstan-param  array<TKey,T> ...$array
2235
     * @phpstan-return static
2236
     * @psalm-mutation-free
2237
     */
2238
    public function diff(array ...$array): self
2239
    {
2240
        if (\count($array) > 1) {
91 ✔
2241
            $array = \array_merge([], ...$array);
7 ✔
2242
        } else {
2243
            $array = $array[0];
91 ✔
2244
        }
2245

2246
        $generator = function () use ($array): \Generator {
91 ✔
2247
            foreach ($this->getGenerator() as $key => $value) {
91 ✔
2248
                if (\in_array($value, $array, true) === false) {
77 ✔
2249
                    yield $key => $value;
35 ✔
2250
                }
2251
            }
2252
        };
91 ✔
2253

2254
        return static::create(
91 ✔
2255
            $generator,
91 ✔
2256
            $this->iteratorClass,
91 ✔
2257
            false
91 ✔
2258
        );
91 ✔
2259
    }
2260

2261
    /**
2262
     * Return elements where the keys are only in the current array.
2263
     *
2264
     * @param array ...$array
2265
     *
2266
     * @return static
2267
     *                <p>(Immutable)</p>
2268
     *
2269
     * @phpstan-param  array<TKey,T> ...$array
2270
     * @phpstan-return static
2271
     * @psalm-mutation-free
2272
     */
2273
    public function diffKey(array ...$array): self
2274
    {
2275
        if (\count($array) > 1) {
63 ✔
2276
            $array = \array_replace([], ...$array);
7 ✔
2277
        } else {
2278
            $array = $array[0];
56 ✔
2279
        }
2280

2281
        $generator = function () use ($array): \Generator {
63 ✔
2282
            foreach ($this->getGenerator() as $key => $value) {
63 ✔
2283
                if (\array_key_exists($key, $array) === false) {
56 ✔
2284
                    yield $key => $value;
14 ✔
2285
                }
2286
            }
2287
        };
63 ✔
2288

2289
        return static::create(
63 ✔
2290
            $generator,
63 ✔
2291
            $this->iteratorClass,
63 ✔
2292
            false
63 ✔
2293
        );
63 ✔
2294
    }
2295

2296
    /**
2297
     * Return elements where the values and keys are only in the current array.
2298
     *
2299
     * @param array ...$array
2300
     *
2301
     * @return static
2302
     *                <p>(Immutable)</p>
2303
     *
2304
     * @phpstan-param  array<TKey,T> $array
2305
     * @phpstan-return static
2306
     * @psalm-mutation-free
2307
     */
2308
    public function diffKeyAndValue(array ...$array): self
2309
    {
2310
        if (\count($array) > 1) {
63 ✔
2311
            $array = \array_merge([], ...$array);
7 ✔
2312
        } else {
2313
            $array = $array[0];
56 ✔
2314
        }
2315

2316
        $generator = function () use ($array): \Generator {
63 ✔
2317
            foreach ($this->getGenerator() as $key => $value) {
63 ✔
2318
                $isset = isset($array[$key]);
56 ✔
2319

2320
                if (
2321
                    !$isset
56 ✔
2322
                    ||
2323
                    $array[$key] !== $value
56 ✔
2324
                ) {
2325
                    yield $key => $value;
28 ✔
2326
                }
2327
            }
2328
        };
63 ✔
2329

2330
        return static::create(
63 ✔
2331
            $generator,
63 ✔
2332
            $this->iteratorClass,
63 ✔
2333
            false
63 ✔
2334
        );
63 ✔
2335
    }
2336

2337
    /**
2338
     * Return elements where the values are only in the current multi-dimensional array.
2339
     *
2340
     * EXAMPLE: <code>
2341
     * a([1 => [1 => 1], 2 => [2 => 2]])->diffRecursive([1 => [1 => 1]]); // Arrayy[2 => [2 => 2]]
2342
     * </code>
2343
     *
2344
     * @param array                 $array
2345
     * @param array|\Generator|null $helperVariableForRecursion <p>(only for internal usage)</p>
2346
     *
2347
     * @return static
2348
     *                <p>(Immutable)</p>
2349
     *
2350
     * @phpstan-param  array<TKey,T> $array
2351
     * @phpstan-param  null|array<TKey,T>|\Generator<TKey,T> $helperVariableForRecursion
2352
     * @phpstan-return static
2353
     * @psalm-mutation-free
2354
     */
2355
    public function diffRecursive(array $array = [], $helperVariableForRecursion = null): self
2356
    {
2357
        // init
2358
        $result = [];
7 ✔
2359

2360
        if (
2361
            $helperVariableForRecursion !== null
7 ✔
2362
            &&
2363
            \is_array($helperVariableForRecursion)
7 ✔
2364
        ) {
2365
            $arrayForTheLoop = $helperVariableForRecursion;
×
2366
        } else {
2367
            $arrayForTheLoop = $this->getGenerator();
7 ✔
2368
        }
2369

2370
        foreach ($arrayForTheLoop as $key => $value) {
7 ✔
2371
            if ($value instanceof self) {
7 ✔
2372
                $value = $value->toArray();
7 ✔
2373
            }
2374

2375
            if (\array_key_exists($key, $array)) {
7 ✔
2376
                if ($value !== $array[$key]) {
7 ✔
2377
                    $result[$key] = $value;
7 ✔
2378
                }
2379
            } else {
2380
                $result[$key] = $value;
7 ✔
2381
            }
2382
        }
2383

2384
        return static::create(
7 ✔
2385
            /* @phpstan-ignore-next-line argument.type */
2386
            $result,
7 ✔
2387
            $this->iteratorClass,
7 ✔
2388
            false
7 ✔
2389
        );
7 ✔
2390
    }
2391

2392
    /**
2393
     * Return elements where the values that are only in the new $array.
2394
     *
2395
     * EXAMPLE: <code>
2396
     * a([1 => 1])->diffReverse([1 => 1, 2 => 2]); // Arrayy[2 => 2]
2397
     * </code>
2398
     *
2399
     * @param array $array
2400
     *
2401
     * @return static
2402
     *                <p>(Immutable)</p>
2403
     *
2404
     * @phpstan-param  array<TKey,T> $array
2405
     * @phpstan-return static
2406
     * @psalm-mutation-free
2407
     */
2408
    public function diffReverse(array $array = []): self
2409
    {
2410
        return static::create(
56 ✔
2411
            /* @phpstan-ignore-next-line argument.type */
2412
            \array_diff($array, $this->toArray()),
56 ✔
2413
            $this->iteratorClass,
56 ✔
2414
            false
56 ✔
2415
        );
56 ✔
2416
    }
2417

2418
    /**
2419
     * Divide an array into two arrays. One with keys and the other with values.
2420
     *
2421
     * EXAMPLE: <code>
2422
     * a(['a' => 1, 'b' => ''])->divide(); // Arrayy[Arrayy['a', 'b'], Arrayy[1, '']]
2423
     * </code>
2424
     *
2425
     * @return static
2426
     *                <p>(Immutable)</p>
2427
     *
2428
     * @phpstan-return static
2429
     * @psalm-mutation-free
2430
     */
2431
    public function divide(): self
2432
    {
2433
        return static::create(
7 ✔
2434
            /* @phpstan-ignore-next-line argument.type */
2435
            [
7 ✔
2436
                $this->keys(),
7 ✔
2437
                $this->values(),
7 ✔
2438
            ],
7 ✔
2439
            $this->iteratorClass,
7 ✔
2440
            false
7 ✔
2441
        );
7 ✔
2442
    }
2443

2444
    /**
2445
     * Iterate over the current array and modify the array's value.
2446
     *
2447
     * EXAMPLE: <code>
2448
     * $result = A::create();
2449
     * $closure = function ($value) {
2450
     *     return ':' . $value . ':';
2451
     * };
2452
     * a(['foo', 'bar' => 'bis'])->each($closure); // Arrayy[':foo:', 'bar' => ':bis:']
2453
     * </code>
2454
     *
2455
     * @param \Closure $closure
2456
     *
2457
     * @return static
2458
     *                <p>(Immutable)</p>
2459
     *
2460
     * @template TEach
2461
     *                 <p>The output value type.</p>
2462
     *
2463
     * @phpstan-param \Closure(T,?TKey):TEach $closure
2464
     * @phpstan-return static<TKey,TEach,array<TKey,TEach>>
2465
     * @psalm-mutation-free
2466
     */
2467
    public function each(\Closure $closure): self
2468
    {
2469
        // init
2470
        $array = [];
49 ✔
2471

2472
        foreach ($this->getGenerator() as $key => $value) {
49 ✔
2473
            $array[$key] = $closure($value, $key);
49 ✔
2474
        }
2475

2476
        return static::create( // @phpstan-ignore return.type (create() is intentionally re-parameterized with TEach)
49 ✔
2477
            /* @phpstan-ignore-next-line argument.type */
2478
            $array,
49 ✔
2479
            $this->iteratorClass,
49 ✔
2480
            false
49 ✔
2481
        );
49 ✔
2482
    }
2483

2484
    /**
2485
     * Sets the internal iterator to the last element in the array and returns this element.
2486
     *
2487
     * @return false|mixed
2488
     *
2489
     * @phpstan-return T|false
2490
     */
2491
    public function end()
2492
    {
2493
        if ($this->generator) {
×
2494
            $count = $this->count();
×
2495
            if ($count === 0) {
×
2496
                return false;
×
2497
            }
2498

2499
            $counter = 0;
×
2500
            /** @noinspection PhpUnusedLocalVariableInspection */
2501
            foreach ($this->getIterator() as $item) {
×
2502
                if (++$counter === $count - 1) {
×
2503
                    break;
×
2504
                }
2505
            }
2506
        }
2507

2508
        return \end($this->array);
×
2509
    }
2510

2511
    /**
2512
     * Check if a value is in the current array using a closure.
2513
     *
2514
     * EXAMPLE: <code>
2515
     * $callable = function ($value, $key) {
2516
     *     return 2 === $key and 'two' === $value;
2517
     * };
2518
     * a(['foo', 2 => 'two'])->exists($callable); // true
2519
     * </code>
2520
     *
2521
     * @param \Closure $closure
2522
     *
2523
     * @return bool
2524
     *              <p>Returns true if the given value is found, false otherwise.</p>
2525
     *
2526
     * @phpstan-param \Closure(T,TKey):bool $closure
2527
     */
2528
    public function exists(\Closure $closure): bool
2529
    {
2530
        // init
2531
        $isExists = false;
28 ✔
2532

2533
        foreach ($this->getGenerator() as $key => $value) {
28 ✔
2534
            if ($closure($value, $key)) {
21 ✔
2535
                $isExists = true;
7 ✔
2536

2537
                break;
7 ✔
2538
            }
2539
        }
2540

2541
        return $isExists;
28 ✔
2542
    }
2543

2544
    /**
2545
     * Fill the array until "$num" with "$default" values.
2546
     *
2547
     * EXAMPLE: <code>
2548
     * a(['bar'])->fillWithDefaults(3, 'foo'); // Arrayy['bar', 'foo', 'foo']
2549
     * </code>
2550
     *
2551
     * @param int   $num
2552
     * @param mixed $default
2553
     *
2554
     * @return static
2555
     *                <p>(Immutable)</p>
2556
     *
2557
     * @phpstan-param T $default
2558
     * @phpstan-return static
2559
     * @psalm-mutation-free
2560
     */
2561
    public function fillWithDefaults(int $num, $default = null): self
2562
    {
2563
        if ($num < 0) {
56 ✔
2564
            throw new \InvalidArgumentException('The $num parameter can only contain non-negative values.');
7 ✔
2565
        }
2566

2567
        $this->generatorToArray();
49 ✔
2568

2569
        $tmpArray = $this->array;
49 ✔
2570

2571
        $count = \count($tmpArray);
49 ✔
2572

2573
        while ($count < $num) {
49 ✔
2574
            $tmpArray[] = $default;
28 ✔
2575
            ++$count;
28 ✔
2576
        }
2577

2578
        return static::create(
49 ✔
2579
            /* @phpstan-ignore-next-line argument.type */
2580
            $tmpArray,
49 ✔
2581
            $this->iteratorClass,
49 ✔
2582
            false
49 ✔
2583
        );
49 ✔
2584
    }
2585

2586
    /**
2587
     * Find all items in an array that pass the truth test.
2588
     *
2589
     * EXAMPLE: <code>
2590
     * $closure = function ($value) {
2591
     *     return $value % 2 !== 0;
2592
     * }
2593
     * a([1, 2, 3, 4])->filter($closure); // Arrayy[0 => 1, 2 => 3]
2594
     * </code>
2595
     *
2596
     * @param \Closure|null $closure [optional] <p>
2597
     *                               The callback function to use
2598
     *                               </p>
2599
     *                               <p>
2600
     *                               If no callback is supplied, all entries of
2601
     *                               input equal to false (see
2602
     *                               converting to
2603
     *                               boolean) will be removed.
2604
     *                               </p>
2605
     * @param int           $flag    [optional] <p>
2606
     *                               Flag determining what arguments are sent to <i>callback</i>:
2607
     *                               </p>
2608
     *                               <ul>
2609
     *                               <li>
2610
     *                               <b>ARRAY_FILTER_USE_KEY</b> (1) - pass key as the only argument
2611
     *                               to <i>callback</i> instead of the value
2612
     *                               </li>
2613
     *                               <li>
2614
     *                               <b>ARRAY_FILTER_USE_BOTH</b> (2) - pass both value and key as
2615
     *                               arguments to <i>callback</i> instead of the value
2616
     *                               </li>
2617
     *                               </ul>
2618
     *
2619
     * @return static
2620
     *                <p>(Immutable)</p>
2621
     *
2622
     * @phpstan-param null|(\Closure(T,TKey=):bool)|(\Closure(T):bool)|(\Closure(TKey):bool) $closure
2623
     * @phpstan-return static
2624
     * @psalm-mutation-free
2625
     */
2626
    public function filter($closure = null, int $flag = \ARRAY_FILTER_USE_BOTH)
2627
    {
2628
        if (!$closure) {
91 ✔
2629
            return $this->clean();
7 ✔
2630
        }
2631

2632
        if ($flag === \ARRAY_FILTER_USE_KEY) {
91 ✔
2633
            $generator = function () use ($closure) {
7 ✔
2634
                foreach ($this->getGenerator() as $key => $value) {
7 ✔
2635
                    if ($closure($key) === true) {
7 ✔
2636
                        yield $key => $value;
7 ✔
2637
                    }
2638
                }
2639
            };
7 ✔
2640
        } elseif ($flag === \ARRAY_FILTER_USE_BOTH) {
91 ✔
2641
            /** @noinspection PhpSillyAssignmentInspection - hack for phpstan - https://github.com/phpstan/phpstan/issues/4192 */
2642
            /** @phpstan-var \Closure(T,TKey):bool $closure */
2643
            $closure = $closure;
91 ✔
2644

2645
            $generator = function () use ($closure) {
91 ✔
2646
                foreach ($this->getGenerator() as $key => $value) {
84 ✔
2647
                    if ($closure($value, $key) === true) {
77 ✔
2648
                        yield $key => $value;
70 ✔
2649
                    }
2650
                }
2651
            };
91 ✔
2652
        } else {
2653
            $generator = function () use ($closure) {
7 ✔
2654
                foreach ($this->getGenerator() as $key => $value) {
7 ✔
2655
                    if ($closure($value) === true) {
7 ✔
2656
                        yield $key => $value;
7 ✔
2657
                    }
2658
                }
2659
            };
7 ✔
2660
        }
2661

2662
        return static::create(
91 ✔
2663
            $generator,
91 ✔
2664
            $this->iteratorClass,
91 ✔
2665
            false
91 ✔
2666
        );
91 ✔
2667
    }
2668

2669
    /**
2670
     * Filters an array of objects (or a numeric array of associative arrays) based on the value of a particular
2671
     * property within that.
2672
     *
2673
     * @param string      $property
2674
     * @param mixed       $value
2675
     * @param string|null $comparisonOp
2676
     *                                  <p>
2677
     *                                  'eq' (equals),<br />
2678
     *                                  'gt' (greater),<br />
2679
     *                                  'gte' || 'ge' (greater or equals),<br />
2680
     *                                  'lt' (less),<br />
2681
     *                                  'lte' || 'le' (less or equals),<br />
2682
     *                                  'ne' (not equals),<br />
2683
     *                                  'contains',<br />
2684
     *                                  'notContains',<br />
2685
     *                                  'newer' (via strtotime),<br />
2686
     *                                  'older' (via strtotime),<br />
2687
     *                                  </p>
2688
     *
2689
     * @return static
2690
     *                <p>(Immutable)</p>
2691
     *
2692
     * @phpstan-param array<array-key, mixed>|T $value
2693
     * @phpstan-return static
2694
     * @psalm-mutation-free
2695
     *
2696
     * @psalm-suppress MissingClosureReturnType
2697
     * @psalm-suppress MissingClosureParamType
2698
     */
2699
    public function filterBy(
2700
        string $property,
2701
        $value,
2702
        ?string $comparisonOp = null
2703
    ): self {
2704
        if (!$comparisonOp) {
7 ✔
2705
            $comparisonOp = \is_array($value) ? 'contains' : 'eq';
7 ✔
2706
        }
2707

2708
        $ops = [
7 ✔
2709
            'eq' => static function ($item, $prop, $value): bool {
7 ✔
2710
                return $item[$prop] === $value;
7 ✔
2711
            },
7 ✔
2712
            'gt' => static function ($item, $prop, $value): bool {
7 ✔
2713
                return $item[$prop] > $value;
×
2714
            },
7 ✔
2715
            'ge' => static function ($item, $prop, $value): bool {
7 ✔
2716
                return $item[$prop] >= $value;
×
2717
            },
7 ✔
2718
            'gte' => static function ($item, $prop, $value): bool {
7 ✔
2719
                return $item[$prop] >= $value;
×
2720
            },
7 ✔
2721
            'lt' => static function ($item, $prop, $value): bool {
7 ✔
2722
                return $item[$prop] < $value;
7 ✔
2723
            },
7 ✔
2724
            'le' => static function ($item, $prop, $value): bool {
7 ✔
2725
                return $item[$prop] <= $value;
×
2726
            },
7 ✔
2727
            'lte' => static function ($item, $prop, $value): bool {
7 ✔
2728
                return $item[$prop] <= $value;
×
2729
            },
7 ✔
2730
            'ne' => static function ($item, $prop, $value): bool {
7 ✔
2731
                return $item[$prop] !== $value;
×
2732
            },
7 ✔
2733
            'contains' => static function ($item, $prop, $value): bool {
7 ✔
2734
                return \in_array($item[$prop], (array) $value, true);
7 ✔
2735
            },
7 ✔
2736
            'notContains' => static function ($item, $prop, $value): bool {
7 ✔
2737
                return !\in_array($item[$prop], (array) $value, true);
×
2738
            },
7 ✔
2739
            'newer' => static function ($item, $prop, $value): bool {
7 ✔
2740
                return \strtotime($item[$prop]) > \strtotime($value);
×
2741
            },
7 ✔
2742
            'older' => static function ($item, $prop, $value): bool {
7 ✔
2743
                return \strtotime($item[$prop]) < \strtotime($value);
×
2744
            },
7 ✔
2745
        ];
7 ✔
2746

2747
        $result = \array_values(
7 ✔
2748
            \array_filter(
7 ✔
2749
                $this->toArray(false, true),
7 ✔
2750
                static function ($item) use (
7 ✔
2751
                    $property,
7 ✔
2752
                    $value,
7 ✔
2753
                    $ops,
7 ✔
2754
                    $comparisonOp
7 ✔
2755
                ) {
7 ✔
2756
                    $item = (array) $item;
7 ✔
2757
                    /* @phpstan-ignore-next-line argument.type */
2758
                    $itemArrayy = static::create($item);
7 ✔
2759
                    $item[$property] = $itemArrayy->get($property, []);
7 ✔
2760

2761
                    return $ops[$comparisonOp]($item, $property, $value);
7 ✔
2762
                }
7 ✔
2763
            )
7 ✔
2764
        );
7 ✔
2765

2766
        return static::create(
7 ✔
2767
            /* @phpstan-ignore-next-line argument.type */
2768
            $result,
7 ✔
2769
            $this->iteratorClass,
7 ✔
2770
            false
7 ✔
2771
        );
7 ✔
2772
    }
2773

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

2801
        return false;
21 ✔
2802
    }
2803

2804
    /**
2805
     * Find the key of the first item in an array that passes the truth test, otherwise return false.
2806
     *
2807
     * EXAMPLE: <code>
2808
     * $search = 'foo';
2809
     * $closure = function ($value, $key) use ($search) {
2810
     *     return $value === $search;
2811
     * };
2812
     * a(['foo', 'bar', 'lall'])->findKey($closure); // 0
2813
     * </code>
2814
     *
2815
     * @param \Closure $closure
2816
     *
2817
     * @return false|int|string
2818
     *                          <p>Return false if we did not find the key.</p>
2819
     *
2820
     * @phpstan-param \Closure(T,TKey):bool $closure
2821
     * @phpstan-return TKey|false
2822
     */
2823
    public function findKey(\Closure $closure)
2824
    {
2825
        foreach ($this->getGenerator() as $key => $value) {
91 ✔
2826
            if ($closure($value, $key)) {
84 ✔
2827
                return $key;
70 ✔
2828
            }
2829
        }
2830

2831
        return false;
28 ✔
2832
    }
2833

2834
    /**
2835
     * find by ...
2836
     *
2837
     * EXAMPLE: <code>
2838
     * $array = [
2839
     *     0 => ['id' => 123, 'name' => 'foo', 'group' => 'primary', 'value' => 123456, 'when' => '2014-01-01'],
2840
     *     1 => ['id' => 456, 'name' => 'bar', 'group' => 'primary', 'value' => 1468, 'when' => '2014-07-15'],
2841
     * ];
2842
     * a($array)->filterBy('name', 'foo'); // Arrayy[0 => ['id' => 123, 'name' => 'foo', 'group' => 'primary', 'value' => 123456, 'when' => '2014-01-01']]
2843
     * </code>
2844
     *
2845
     * @param string $property
2846
     * @param mixed  $value
2847
     * @param string $comparisonOp
2848
     *
2849
     * @return static
2850
     *                <p>(Immutable)</p>
2851
     *
2852
     * @phpstan-param array<array-key, mixed>|T $value
2853
     * @phpstan-return static
2854
     * @psalm-mutation-free
2855
     */
2856
    public function findBy(string $property, $value, string $comparisonOp = 'eq'): self
2857
    {
2858
        return $this->filterBy($property, $value, $comparisonOp);
7 ✔
2859
    }
2860

2861
    /**
2862
     * Get the first value from the current array.
2863
     *
2864
     * EXAMPLE: <code>
2865
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->first(); // 'foo'
2866
     * </code>
2867
     *
2868
     * @return mixed|null
2869
     *                    <p>Return null if there wasn't a element.</p>
2870
     *
2871
     * @phpstan-return T|null
2872
     * @psalm-mutation-free
2873
     */
2874
    public function first()
2875
    {
2876
        $key_first = $this->firstKey();
158 ✔
2877
        if ($key_first === null) {
158 ✔
2878
            return null;
21 ✔
2879
        }
2880

2881
        return $this->get($key_first);
137 ✔
2882
    }
2883

2884
    /**
2885
     * Get the first key from the current array.
2886
     *
2887
     * @return mixed|null
2888
     *                    <p>Return null if there wasn't a element.</p>
2889
     *
2890
     * @phpstan-return TKey|null
2891
     *
2892
     * @psalm-mutation-free
2893
     */
2894
    public function firstKey()
2895
    {
2896
        $this->generatorToArray();
207 ✔
2897

2898
        /** @phpstan-var TKey|null $return - help for phpstan */
2899
        $return = \array_key_first($this->array);
207 ✔
2900

2901
        return $return;
207 ✔
2902
    }
2903

2904
    /**
2905
     * Get the first value(s) from the current array.
2906
     * And will return an empty array if there was no first entry.
2907
     *
2908
     * EXAMPLE: <code>
2909
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->firstsImmutable(2); // Arrayy[0 => 'foo', 1 => 'bar']
2910
     * </code>
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 firstsImmutable(?int $number = null): self
2921
    {
2922
        $arrayTmp = $this->toArray();
259 ✔
2923

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

2930
        return static::create(
259 ✔
2931
            /* @phpstan-ignore-next-line argument.type */
2932
            $array,
259 ✔
2933
            $this->iteratorClass,
259 ✔
2934
            false
259 ✔
2935
        );
259 ✔
2936
    }
2937

2938
    /**
2939
     * Get the first value(s) from the current array.
2940
     * And will return an empty array if there was no first entry.
2941
     *
2942
     * @param int|null $number <p>How many values you will take?</p>
2943
     *
2944
     * @return static
2945
     *                <p>(Immutable)</p>
2946
     *
2947
     * @phpstan-return static
2948
     * @psalm-mutation-free
2949
     */
2950
    public function firstsKeys(?int $number = null): self
2951
    {
2952
        $arrayTmp = $this->keys()->toArray();
21 ✔
2953

2954
        if ($number === null) {
21 ✔
2955
            $array = (array) \array_shift($arrayTmp);
×
2956
        } else {
2957
            $array = \array_splice($arrayTmp, 0, $number);
21 ✔
2958
        }
2959

2960
        return static::create(
21 ✔
2961
            /* @phpstan-ignore-next-line argument.type */
2962
            $array,
21 ✔
2963
            $this->iteratorClass,
21 ✔
2964
            false
21 ✔
2965
        );
21 ✔
2966
    }
2967

2968
    /**
2969
     * Get and remove the first value(s) from the current array.
2970
     * And will return an empty array if there was no first entry.
2971
     *
2972
     * EXAMPLE: <code>
2973
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->firstsMutable(); // 'foo'
2974
     * </code>
2975
     *
2976
     * @param int|null $number <p>How many values you will take?</p>
2977
     *
2978
     * @return $this
2979
     *               <p>(Mutable)</p>
2980
     *
2981
     * @phpstan-return ($number is null ? static : static)
2982
     */
2983
    public function firstsMutable(?int $number = null): self
2984
    {
2985
        $this->generatorToArray();
238 ✔
2986

2987
        if ($number === null) {
238 ✔
2988
            $shift = \array_shift($this->array);
133 ✔
2989
            /* @phpstan-ignore assign.propertyType */
2990
            $this->array = $shift !== null ? [$shift] : [];
133 ✔
2991
        } else {
2992
            $splice = \array_splice($this->array, 0, $number);
105 ✔
2993
            $this->array = $splice;
105 ✔
2994
        }
2995

2996
        return $this;
238 ✔
2997
    }
2998

2999
    /**
3000
     * Exchanges all keys with their associated values in an array.
3001
     *
3002
     * EXAMPLE: <code>
3003
     * a([0 => 'foo', 1 => 'bar'])->flip(); // Arrayy['foo' => 0, 'bar' => 1]
3004
     * </code>
3005
     *
3006
     * @return static
3007
     *                <p>(Immutable)</p>
3008
     *
3009
     * @phpstan-return static
3010
     * @psalm-mutation-free
3011
     */
3012
    public function flip(): self
3013
    {
3014
        $generator = function (): \Generator {
7 ✔
3015
            foreach ($this->getGenerator() as $key => $value) {
7 ✔
3016
                yield (string) $value => $key;
7 ✔
3017
            }
3018
        };
7 ✔
3019

3020
        return static::create(
7 ✔
3021
            $generator,
7 ✔
3022
            $this->iteratorClass,
7 ✔
3023
            false
7 ✔
3024
        );
7 ✔
3025
    }
3026

3027
    /**
3028
     * Get a value from an array (optional using dot-notation).
3029
     *
3030
     * EXAMPLE: <code>
3031
     * $arrayy = a(['user' => ['lastname' => 'Moelleken']]);
3032
     * $arrayy->get('user.lastname'); // 'Moelleken'
3033
     * // ---
3034
     * $arrayy = new A();
3035
     * $arrayy['user'] = ['lastname' => 'Moelleken'];
3036
     * $arrayy['user.firstname'] = 'Lars';
3037
     * $arrayy['user']['lastname']; // Moelleken
3038
     * $arrayy['user.lastname']; // Moelleken
3039
     * $arrayy['user.firstname']; // Lars
3040
     * </code>
3041
     *
3042
     * @param int|string $key
3043
     *                                   <p>The key to look for.</p>
3044
     * @param mixed      $fallback
3045
     *                                   <p>Value to fallback to.</p>
3046
     * @param array|null $array
3047
     *                                   <p>The array to get from, if it's set to "null" we use the current array from the
3048
     *                                   class.</p>
3049
     * @param bool       $useByReference
3050
     *
3051
     * @return mixed|static
3052
     *
3053
     * @phpstan-param TKey $key
3054
     * @phpstan-param array<array-key,mixed>|array<TKey,T> $array
3055
     * @psalm-mutation-free
3056
     */
3057
    public function get(
3058
        $key = null,
3059
        $fallback = null,
3060
        ?array $array = null,
3061
        bool $useByReference = false
3062
    ) {
3063
        if ($array === null && $key === null) {
2,000 ✔
3064
            if ($useByReference) {
7 ✔
3065
                return $this;
×
3066
            }
3067

3068
            return clone $this;
7 ✔
3069
        }
3070

3071
        if ($array !== null) {
2,000 ✔
3072
            if ($useByReference) {
28 ✔
3073
                $usedArray = &$array;
×
3074
            } else {
3075
                $usedArray = $array;
28 ✔
3076
            }
3077
        } else {
3078
            $this->generatorToArray();
1,979 ✔
3079

3080
            if ($useByReference) {
1,979 ✔
3081
                $usedArray = &$this->array;
1,181 ✔
3082
            } else {
3083
                $usedArray = $this->array;
935 ✔
3084
            }
3085
        }
3086

3087
        if ($key === null) {
2,000 ✔
3088
            return static::create(
7 ✔
3089
                /* @phpstan-ignore-next-line argument.type */
3090
                [],
7 ✔
3091
                $this->iteratorClass,
7 ✔
3092
                false
7 ✔
3093
            )->createByReference($usedArray);
7 ✔
3094
        }
3095

3096
        // php cast "bool"-index into "int"-index
3097
        /* @phpstan-ignore identical.alwaysFalse */
3098
        if ((bool) $key === $key) {
2,000 ✔
3099
            $key = (int) $key;
×
3100
        }
3101

3102
        if (\array_key_exists($key, $usedArray) === true) {
2,000 ✔
3103
            if (\is_array($usedArray[$key])) {
1,727 ✔
3104
                return static::create(
144 ✔
3105
                    /* @phpstan-ignore-next-line argument.type */
3106
                    [],
144 ✔
3107
                    $this->iteratorClass,
144 ✔
3108
                    false
144 ✔
3109
                )->createByReference($usedArray[$key]);
144 ✔
3110
            }
3111

3112
            return $usedArray[$key];
1,615 ✔
3113
        }
3114

3115
        // crawl through array, get key according to object or not
3116
        $usePath = false;
441 ✔
3117
        if (
3118
            $this->pathSeparator
441 ✔
3119
            &&
3120
            (string) $key === $key
441 ✔
3121
            &&
3122
            \strpos($key, $this->pathSeparator) !== false
441 ✔
3123
        ) {
3124
            $segments = \explode($this->pathSeparator, (string) $key);
224 ✔
3125
            $usePath = true;
224 ✔
3126
            $usedArrayTmp = $usedArray; // do not use the reference for dot-annotations
224 ✔
3127

3128
            foreach ($segments as $segment) {
224 ✔
3129
                if (
3130
                    (
3131
                        \is_array($usedArrayTmp)
224 ✔
3132
                        ||
224 ✔
3133
                        $usedArrayTmp instanceof \ArrayAccess
224 ✔
3134
                    )
3135
                    &&
3136
                    isset($usedArrayTmp[$segment])
224 ✔
3137
                ) {
3138
                    $usedArrayTmp = $usedArrayTmp[$segment];
217 ✔
3139

3140
                    continue;
217 ✔
3141
                }
3142

3143
                if (
3144
                    \is_object($usedArrayTmp) === true
105 ✔
3145
                    &&
3146
                    \property_exists($usedArrayTmp, $segment)
105 ✔
3147
                ) {
3148
                    $usedArrayTmp = $usedArrayTmp->{$segment};
7 ✔
3149

3150
                    continue;
7 ✔
3151
                }
3152

3153
                if ($segments[0] === '*') {
98 ✔
3154
                    $segmentsTmp = $segments;
7 ✔
3155
                    unset($segmentsTmp[0]);
7 ✔
3156
                    $keyTmp = \implode('.', $segmentsTmp);
7 ✔
3157
                    $returnTmp = static::create(
7 ✔
3158
                        /* @phpstan-ignore-next-line argument.type */
3159
                        [],
7 ✔
3160
                        $this->iteratorClass,
7 ✔
3161
                        false
7 ✔
3162
                    );
7 ✔
3163
                    foreach ($this->getAll() as $dataTmp) {
7 ✔
3164
                        if ($dataTmp instanceof self) {
7 ✔
3165
                            $returnTmp->add($dataTmp->get($keyTmp));
×
3166

3167
                            continue;
×
3168
                        }
3169

3170
                        if (
3171
                            (
3172
                                \is_array($dataTmp)
7 ✔
3173
                                ||
7 ✔
3174
                                $dataTmp instanceof \ArrayAccess
7 ✔
3175
                            )
3176
                            &&
3177
                            isset($dataTmp[$keyTmp])
7 ✔
3178
                        ) {
3179
                            $returnTmp->add($dataTmp[$keyTmp]);
×
3180

3181
                            continue;
×
3182
                        }
3183

3184
                        if (
3185
                            \is_object($dataTmp) === true
7 ✔
3186
                            &&
3187
                            \property_exists($dataTmp, $keyTmp)
7 ✔
3188
                        ) {
3189
                            $returnTmp->add($dataTmp->{$keyTmp});
7 ✔
3190

3191
                            continue;
7 ✔
3192
                        }
3193
                    }
3194

3195
                    if ($returnTmp->count() > 0) {
7 ✔
3196
                        return $returnTmp;
7 ✔
3197
                    }
3198
                }
3199

3200
                return $fallback instanceof \Closure ? $fallback() : $fallback;
91 ✔
3201
            }
3202

3203
            if (\is_array($usedArrayTmp)) {
203 ✔
3204
                return static::create(
42 ✔
3205
                    /* @phpstan-ignore-next-line argument.type */
3206
                    [],
42 ✔
3207
                    $this->iteratorClass,
42 ✔
3208
                    false
42 ✔
3209
                )->createByReference($usedArrayTmp);
42 ✔
3210
            }
3211

3212
            return $usedArrayTmp;
203 ✔
3213
        }
3214

3215
        if (!isset($usedArray[$key])) {
217 ✔
3216
            return $fallback instanceof \Closure ? $fallback() : $fallback;
217 ✔
3217
        }
3218

3219
        return static::create(
×
3220
            /* @phpstan-ignore-next-line argument.type */
3221
            [],
×
3222
            $this->iteratorClass,
×
3223
            false
×
3224
        )->createByReference($usedArray);
×
3225
    }
3226

3227
    /**
3228
     * alias: for "Arrayy->toArray()"
3229
     *
3230
     * @return array
3231
     *
3232
     * @see          Arrayy::getArray()
3233
     *
3234
     * @phpstan-return array<TKey,T>
3235
     */
3236
    public function getAll(): array
3237
    {
3238
        /** @var array<TKey,T> $return */
3239
        $return = $this->toArray();
105 ✔
3240

3241
        return $return;
105 ✔
3242
    }
3243

3244
    /**
3245
     * Get the current array from the "Arrayy"-object.
3246
     *
3247
     * alias for "toArray()"
3248
     *
3249
     * @param bool $convertAllArrayyElements <p>
3250
     *                                       Convert all Child-"Arrayy" objects also to arrays.
3251
     *                                       </p>
3252
     * @param bool $preserveKeys             <p>
3253
     *                                       e.g.: A generator maybe return the same key more than once,
3254
     *                                       so maybe you will ignore the keys.
3255
     *                                       </p>
3256
     *
3257
     * @return array
3258
     *
3259
     * @phpstan-return array<array-key,T>|array<TKey,T>
3260
     * @psalm-mutation-free
3261
     *
3262
     * @see Arrayy::toArray()
3263
     */
3264
    public function getArray(
3265
        bool $convertAllArrayyElements = false,
3266
        bool $preserveKeys = true
3267
    ): array {
3268
        return $this->toArray(
3,640 ✔
3269
            $convertAllArrayyElements,
3,640 ✔
3270
            $preserveKeys
3,640 ✔
3271
        );
3,640 ✔
3272
    }
3273

3274
    /**
3275
     * Create an instance from JSON using the built-in mapper.
3276
     *
3277
     * For Arrayy models with property checks enabled, phpdoc array-shape annotations,
3278
     * legacy `@property` definitions, and native declared properties are used for metadata and type checks.
3279
     * Add a property-level `@var` annotation if a native `array` property also needs
3280
     * element-type validation.
3281
     *
3282
     * @param string $json
3283
     *
3284
     * @return static
3285
     *                <p>(Immutable)</p>
3286
     */
3287
    public static function createFromJsonMapper(string $json)
3288
    {
3289
        // init
3290
        $class = static::create();
56 ✔
3291

3292
        $jsonObject = \json_decode($json, false);
56 ✔
3293

3294
        $mapper = new \Arrayy\Mapper\Json();
56 ✔
3295
        $mapper->undefinedPropertyHandler = static function ($object, $key, $jsonValue) use ($class) {
56 ✔
3296
            if ($class->checkPropertiesMismatchInConstructor) {
×
3297
                throw new \TypeError('Property mismatch - input: ' . \print_r(['key' => $key, 'jsonValue' => $jsonValue], true) . ' for object: ' . \get_class($object));
×
3298
            }
3299
        };
56 ✔
3300

3301
        /** @var static $return - hack for phpstan */
3302
        $return = $mapper->map($jsonObject, $class);
56 ✔
3303

3304
        return $return;
35 ✔
3305
    }
3306

3307
    /**
3308
     * @return array<array-key,TypeCheckInterface>|TypeCheckArray<array-key,TypeCheckInterface>
3309
     *
3310
     * @internal
3311
     */
3312
    public function getPhpDocPropertiesFromClass()
3313
    {
3314
        if ($this->properties === []) {
166 ✔
3315
            $this->properties = $this->getPropertiesFromPhpDoc();
96 ✔
3316
        }
3317

3318
        return $this->properties;
166 ✔
3319
    }
3320

3321
    /**
3322
     * Get the current array from the "Arrayy"-object as list.
3323
     *
3324
     * alias for "toList()"
3325
     *
3326
     * @param bool $convertAllArrayyElements <p>
3327
     *                                       Convert all Child-"Arrayy" objects also to arrays.
3328
     *                                       </p>
3329
     *
3330
     * @return array
3331
     *
3332
     * @phpstan-return list<T>
3333
     * @psalm-mutation-free
3334
     *
3335
     * @see Arrayy::toList()
3336
     */
3337
    public function getList(bool $convertAllArrayyElements = false): array
3338
    {
3339
        return $this->toList($convertAllArrayyElements);
7 ✔
3340
    }
3341

3342
    /**
3343
     * Returns the values from a single column of the input array, identified by
3344
     * the $columnKey, can be used to extract data-columns from multi-arrays.
3345
     *
3346
     * EXAMPLE: <code>
3347
     * a([['foo' => 'bar', 'id' => 1], ['foo => 'lall', 'id' => 2]])->getColumn('foo', 'id'); // Arrayy[1 => 'bar', 2 => 'lall']
3348
     * </code>
3349
     *
3350
     * INFO: Optionally, you may provide an $indexKey to index the values in the returned
3351
     *       array by the values from the $indexKey column in the input array.
3352
     *
3353
     * @param int|string|null $columnKey
3354
     * @param int|string|null $indexKey
3355
     *
3356
     * @return static
3357
     *                <p>(Immutable)</p>
3358
     *
3359
     * @phpstan-return static
3360
     * @psalm-mutation-free
3361
     */
3362
    public function getColumn($columnKey = null, $indexKey = null): self
3363
    {
3364
        if ($columnKey === null && $indexKey === null) {
7 ✔
3365
            $generator = function () {
7 ✔
3366
                foreach ($this->getGenerator() as $value) {
7 ✔
3367
                    yield $value;
7 ✔
3368
                }
3369
            };
7 ✔
3370
        } else {
3371
            $generator = function () use ($columnKey, $indexKey) {
7 ✔
3372
                foreach ($this->getGenerator() as $value) {
7 ✔
3373
                    // reset
3374
                    $newKey = null;
7 ✔
3375
                    $newValue = null;
7 ✔
3376
                    $newValueFound = false;
7 ✔
3377

3378
                    if ($indexKey !== null) {
7 ✔
3379
                        foreach ($value as $keyInner => $valueInner) {
7 ✔
3380
                            if ($indexKey === $keyInner) {
7 ✔
3381
                                $newKey = $valueInner;
7 ✔
3382
                            }
3383

3384
                            if ($columnKey === $keyInner) {
7 ✔
3385
                                $newValue = $valueInner;
7 ✔
3386
                                $newValueFound = true;
7 ✔
3387
                            }
3388
                        }
3389
                    } else {
3390
                        foreach ($value as $keyInner => $valueInner) {
7 ✔
3391
                            if ($columnKey === $keyInner) {
7 ✔
3392
                                $newValue = $valueInner;
7 ✔
3393
                                $newValueFound = true;
7 ✔
3394
                            }
3395
                        }
3396
                    }
3397

3398
                    if ($newValueFound === false) {
7 ✔
3399
                        if ($newKey !== null) {
7 ✔
3400
                            yield $newKey => $value;
7 ✔
3401
                        } else {
3402
                            yield $value;
7 ✔
3403
                        }
3404
                    } else {
3405
                        /** @noinspection NestedPositiveIfStatementsInspection */
3406
                        if ($newKey !== null) {
7 ✔
3407
                            yield $newKey => $newValue;
7 ✔
3408
                        } else {
3409
                            yield $newValue;
7 ✔
3410
                        }
3411
                    }
3412
                }
3413
            };
7 ✔
3414
        }
3415

3416
        return static::create(
7 ✔
3417
            $generator,
7 ✔
3418
            $this->iteratorClass,
7 ✔
3419
            false
7 ✔
3420
        );
7 ✔
3421
    }
3422

3423
    /**
3424
     * Get the current array from the "Arrayy"-object as generator by reference.
3425
     *
3426
     * @return \Generator
3427
     *
3428
     * @phpstan-return \Generator<mixed,T>|\Generator<TKey,T>
3429
     */
3430
    public function &getGeneratorByReference(): \Generator
3431
    {
3432
        if ($this->generator instanceof ArrayyRewindableGenerator) {
602 ✔
3433
            foreach ($this->generator as $key => $value) {
119 ✔
3434
                yield $key => $value;
119 ✔
3435
            }
3436

3437
            return;
35 ✔
3438
        }
3439

3440
        // -> false-positive -> see "&$value"
3441
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
3442
        foreach ($this->array as $key => &$value) {
490 ✔
3443
            yield $key => $value;
441 ✔
3444
        }
3445
    }
3446

3447
    /**
3448
     * Get the current array from the "Arrayy"-object as generator.
3449
     *
3450
     * @return \Generator
3451
     *
3452
     * @phpstan-return \Generator<mixed,T>|\Generator<TKey,T>
3453
     * @psalm-mutation-free
3454
     */
3455
    public function getGenerator(): \Generator
3456
    {
3457
        if ($this->generator instanceof ArrayyRewindableGenerator) {
7,922 ✔
3458
            yield from $this->generator;
560 ✔
3459

3460
            return;
560 ✔
3461
        }
3462

3463
        yield from $this->array;
7,908 ✔
3464
    }
3465

3466
    /**
3467
     * Get the current array from the "Arrayy"-object as generator.
3468
     *
3469
     * @return \Generator
3470
     *
3471
     * @phpstan-return \Generator<mixed,T>|\Generator<TKey,T>
3472
     * @psalm-mutation-free
3473
     */
3474
    public function getBackwardsGenerator(): \Generator
3475
    {
3476
        yield from $this->reverseKeepIndex();
28 ✔
3477
    }
3478

3479
    /**
3480
     * alias: for "Arrayy->keys()"
3481
     *
3482
     * @return static
3483
     *                <p>(Immutable)</p>
3484
     *
3485
     * @see          Arrayy::keys()
3486
     *
3487
     * @phpstan-return static
3488
     * @psalm-mutation-free
3489
     */
3490
    public function getKeys()
3491
    {
3492
        return $this->keys();
14 ✔
3493
    }
3494

3495
    /**
3496
     * Get the current array from the "Arrayy"-object as object.
3497
     *
3498
     * @return \stdClass
3499
     */
3500
    public function getObject(): \stdClass
3501
    {
3502
        return self::arrayToObject($this->toArray());
28 ✔
3503
    }
3504

3505
    /**
3506
     * alias: for "Arrayy->randomImmutable()"
3507
     *
3508
     * @return static
3509
     *                <p>(Immutable)</p>
3510
     *
3511
     * @see          Arrayy::randomImmutable()
3512
     *
3513
     * @phpstan-return static
3514
     */
3515
    public function getRandom(): self
3516
    {
3517
        return $this->randomImmutable();
28 ✔
3518
    }
3519

3520
    /**
3521
     * alias: for "Arrayy->randomKey()"
3522
     *
3523
     * @return mixed|null
3524
     *                    <p>Get a key/index or null if there wasn't a key/index.</p>
3525
     *
3526
     * @phpstan-return null|TKey
3527
     *
3528
     * @see Arrayy::randomKey()
3529
     */
3530
    public function getRandomKey()
3531
    {
3532
        return $this->randomKey();
21 ✔
3533
    }
3534

3535
    /**
3536
     * alias: for "Arrayy->randomKeys()"
3537
     *
3538
     * @param int $number
3539
     *
3540
     * @return static
3541
     *                <p>(Immutable)</p>
3542
     *
3543
     * @see          Arrayy::randomKeys()
3544
     *
3545
     * @phpstan-return static
3546
     */
3547
    public function getRandomKeys(int $number): self
3548
    {
3549
        return $this->randomKeys($number);
56 ✔
3550
    }
3551

3552
    /**
3553
     * alias: for "Arrayy->randomValue()"
3554
     *
3555
     * @return mixed|null
3556
     *                    <p>Get a random value or null if there wasn't a value.</p>
3557
     *
3558
     * @phpstan-return null|T
3559
     *
3560
     * @see Arrayy::randomValue()
3561
     */
3562
    public function getRandomValue()
3563
    {
3564
        return $this->randomValue();
21 ✔
3565
    }
3566

3567
    /**
3568
     * alias: for "Arrayy->randomValues()"
3569
     *
3570
     * @param int $number
3571
     *
3572
     * @return static
3573
     *                <p>(Immutable)</p>
3574
     *
3575
     * @see          Arrayy::randomValues()
3576
     *
3577
     * @phpstan-return static
3578
     */
3579
    public function getRandomValues(int $number): self
3580
    {
3581
        return $this->randomValues($number);
42 ✔
3582
    }
3583

3584
    /**
3585
     * Gets all values.
3586
     *
3587
     * @return static
3588
     *                <p>The values of all elements in this array, in the order they
3589
     *                appear in the array.</p>
3590
     *
3591
     * @phpstan-return static
3592
     */
3593
    public function getValues()
3594
    {
3595
        $this->generatorToArray(false);
28 ✔
3596

3597
        return static::create(
28 ✔
3598
            /* @phpstan-ignore-next-line argument.type */
3599
            \array_values($this->array),
28 ✔
3600
            $this->iteratorClass,
28 ✔
3601
            false
28 ✔
3602
        );
28 ✔
3603
    }
3604

3605
    /**
3606
     * Gets all values via Generator.
3607
     *
3608
     * @return \Generator
3609
     *                    <p>The values of all elements in this array, in the order they
3610
     *                    appear in the array as Generator.</p>
3611
     *
3612
     * @phpstan-return \Generator<TKey,T>
3613
     */
3614
    public function getValuesYield(): \Generator
3615
    {
3616
        yield from $this->getGenerator();
28 ✔
3617
    }
3618

3619
    /**
3620
     * Group values from a array according to the results of a closure.
3621
     *
3622
     * @param callable|int|string $grouper  <p>A callable function name.</p>
3623
     * @param bool                $saveKeys
3624
     *
3625
     * @return static
3626
     *                <p>(Immutable)</p>
3627
     *
3628
     * @phpstan-param \Closure(T,TKey):TKey|TKey $grouper
3629
     * @phpstan-return static
3630
     * @psalm-mutation-free
3631
     */
3632
    public function group($grouper, bool $saveKeys = false): self
3633
    {
3634
        // init
3635
        $result = [];
28 ✔
3636

3637
        // Iterate over values, group by property/results from closure.
3638
        foreach ($this->getGenerator() as $key => $value) {
28 ✔
3639
            if (\is_callable($grouper) === true) {
28 ✔
3640
                $groupKey = $grouper($value, $key);
21 ✔
3641
            } else {
3642
                $groupKey = $this->get($grouper);
7 ✔
3643
            }
3644

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

3647
            if ($groupKey instanceof self) {
28 ✔
3648
                $groupKey = $groupKey->toArray();
×
3649
            }
3650

3651
            if ($newValue instanceof self) {
28 ✔
3652
                $newValue = $newValue->toArray();
28 ✔
3653
            }
3654

3655
            // Add to result.
3656
            if ($groupKey !== null) {
28 ✔
3657
                $result[$groupKey] = $newValue;
21 ✔
3658

3659
                if ($saveKeys) {
21 ✔
3660
                    $result[$groupKey][$key] = $value;
14 ✔
3661
                } else {
3662
                    $result[$groupKey][] = $value;
7 ✔
3663
                }
3664
            }
3665
        }
3666

3667
        return static::create(
28 ✔
3668
            /* @phpstan-ignore-next-line argument.type */
3669
            $result,
28 ✔
3670
            $this->iteratorClass,
28 ✔
3671
            false
28 ✔
3672
        );
28 ✔
3673
    }
3674

3675
    /**
3676
     * Check if an array has a given key.
3677
     *
3678
     * @param mixed $key
3679
     *
3680
     * @return bool
3681
     *
3682
     * @phpstan-param null|TKey|TKey[] $key
3683
     */
3684
    public function has($key): bool
3685
    {
3686
        static $UN_FOUND = null;
210 ✔
3687

3688
        if ($UN_FOUND === null) {
210 ✔
3689
            // Generate unique string to use as marker.
3690
            $UN_FOUND = 'arrayy--' . \uniqid('arrayy', true);
7 ✔
3691
        }
3692

3693
        if (\is_array($key)) {
210 ✔
3694
            if ($key === []) {
7 ✔
3695
                return false;
×
3696
            }
3697

3698
            foreach ($key as $keyTmp) {
7 ✔
3699
                $found = ($this->get($keyTmp, $UN_FOUND) !== $UN_FOUND);
7 ✔
3700
                if ($found === false) {
7 ✔
3701
                    return false;
7 ✔
3702
                }
3703
            }
3704

3705
            return true;
7 ✔
3706
        }
3707

3708
        return $this->get($key, $UN_FOUND) !== $UN_FOUND;
203 ✔
3709
    }
3710

3711
    /**
3712
     * Check if an array has a given value.
3713
     *
3714
     * INFO: If you need to search recursive please use ```contains($value, true)```.
3715
     *
3716
     * @param mixed $value
3717
     *
3718
     * @return bool
3719
     *
3720
     * @phpstan-param T $value
3721
     */
3722
    public function hasValue($value): bool
3723
    {
3724
        return $this->contains($value);
7 ✔
3725
    }
3726

3727
    /**
3728
     * Implodes the values of this array.
3729
     *
3730
     * EXAMPLE: <code>
3731
     * a([0 => -9, 1, 2])->implode('|'); // '-9|1|2'
3732
     * </code>
3733
     *
3734
     * @param string $glue
3735
     * @param string $prefix
3736
     *
3737
     * @return string
3738
     * @psalm-mutation-free
3739
     */
3740
    public function implode(string $glue = '', string $prefix = ''): string
3741
    {
3742
        return $prefix . $this->implode_recursive($glue, $this->toArray(), false);
203 ✔
3743
    }
3744

3745
    /**
3746
     * Implodes the keys of this array.
3747
     *
3748
     * @param string $glue
3749
     *
3750
     * @return string
3751
     * @psalm-mutation-free
3752
     */
3753
    public function implodeKeys(string $glue = ''): string
3754
    {
3755
        return $this->implode_recursive($glue, $this->toArray(), true);
56 ✔
3756
    }
3757

3758
    /**
3759
     * Given a list and an iterate-function that returns
3760
     * a key for each element in the list (or a property name),
3761
     * returns an object with an index of each item.
3762
     *
3763
     * @param int|string $key
3764
     *
3765
     * @return static
3766
     *                <p>(Immutable)</p>
3767
     *
3768
     * @phpstan-param array-key $key
3769
     * @phpstan-return static
3770
     * @psalm-mutation-free
3771
     */
3772
    public function indexBy($key): self
3773
    {
3774
        // init
3775
        $results = [];
28 ✔
3776

3777
        foreach ($this->getGenerator() as $a) {
28 ✔
3778
            if (\array_key_exists($key, $a) === true) {
28 ✔
3779
                $results[$a[$key]] = $a;
21 ✔
3780
            }
3781
        }
3782

3783
        return static::create(
28 ✔
3784
            /* @phpstan-ignore-next-line argument.type */
3785
            $results,
28 ✔
3786
            $this->iteratorClass,
28 ✔
3787
            false
28 ✔
3788
        );
28 ✔
3789
    }
3790

3791
    /**
3792
     * alias: for "Arrayy->searchIndex()"
3793
     *
3794
     * @param mixed $value
3795
     *                     <p>The value to search for.</p>
3796
     *
3797
     * @return false|int|string
3798
     *
3799
     * @phpstan-param T $value
3800
     * @phpstan-return false|TKey
3801
     *
3802
     * @see Arrayy::searchIndex()
3803
     */
3804
    public function indexOf($value)
3805
    {
3806
        return $this->searchIndex($value);
28 ✔
3807
    }
3808

3809
    /**
3810
     * Get everything but the last..$to items.
3811
     *
3812
     * EXAMPLE: <code>
3813
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->initial(2); // Arrayy[0 => 'foo']
3814
     * </code>
3815
     *
3816
     * @param int $to
3817
     *
3818
     * @return static
3819
     *                <p>(Immutable)</p>
3820
     *
3821
     * @phpstan-return static
3822
     * @psalm-mutation-free
3823
     */
3824
    public function initial(int $to = 1): self
3825
    {
3826
        return $this->firstsImmutable(\count($this->toArray(), \COUNT_NORMAL) - $to);
84 ✔
3827
    }
3828

3829
    /**
3830
     * Return an array with all elements found in input array.
3831
     *
3832
     * EXAMPLE: <code>
3833
     * a(['foo', 'bar'])->intersection(['bar', 'baz']); // Arrayy['bar']
3834
     * </code>
3835
     *
3836
     * @param array $search
3837
     * @param bool  $keepKeys
3838
     *
3839
     * @return static
3840
     *                <p>(Immutable)</p>
3841
     *
3842
     * @phpstan-param  array<TKey,T> $search
3843
     * @phpstan-return static
3844
     * @psalm-mutation-free
3845
     */
3846
    public function intersection(array $search, bool $keepKeys = false): self
3847
    {
3848
        if ($keepKeys) {
28 ✔
3849
            /**
3850
             * @psalm-suppress MissingClosureReturnType
3851
             * @psalm-suppress MissingClosureParamType
3852
             */
3853
            return static::create(
7 ✔
3854
                /* @phpstan-ignore-next-line argument.type */
3855
                \array_uintersect(
7 ✔
3856
                    $this->toArray(),
7 ✔
3857
                    $search,
7 ✔
3858
                    static function ($a, $b) {
7 ✔
3859
                        return $a === $b ? 0 : -1;
7 ✔
3860
                    }
7 ✔
3861
                ),
7 ✔
3862
                $this->iteratorClass,
7 ✔
3863
                false
7 ✔
3864
            );
7 ✔
3865
        }
3866

3867
        return static::create(
21 ✔
3868
            /* @phpstan-ignore-next-line argument.type */
3869
            \array_values(\array_intersect($this->toArray(), $search)),
21 ✔
3870
            $this->iteratorClass,
21 ✔
3871
            false
21 ✔
3872
        );
21 ✔
3873
    }
3874

3875
    /**
3876
     * Return an array with all elements found in input array.
3877
     *
3878
     * @param array ...$array
3879
     *
3880
     * @return static
3881
     *                <p>(Immutable)</p>
3882
     *
3883
     * @phpstan-param  array<array<TKey,T>> ...$array
3884
     * @phpstan-return static
3885
     * @psalm-mutation-free
3886
     */
3887
    public function intersectionMulti(...$array): self
3888
    {
3889
        return static::create(
7 ✔
3890
            /* @phpstan-ignore-next-line argument.type */
3891
            \array_values(\array_intersect($this->toArray(), ...$array)),
7 ✔
3892
            $this->iteratorClass,
7 ✔
3893
            false
7 ✔
3894
        );
7 ✔
3895
    }
3896

3897
    /**
3898
     * Return a boolean flag which indicates whether the two input arrays have any common elements.
3899
     *
3900
     * EXAMPLE: <code>
3901
     * a(['foo', 'bar'])->intersects(['fΓΆΓΆ', 'bΓ€r']); // false
3902
     * </code>
3903
     *
3904
     * @param array $search
3905
     *
3906
     * @return bool
3907
     *
3908
     * @phpstan-param array<TKey,T> $search
3909
     */
3910
    public function intersects(array $search): bool
3911
    {
3912
        return $this->intersection($search)->count() > 0;
7 ✔
3913
    }
3914

3915
    /**
3916
     * Invoke a function on all of an array's values.
3917
     *
3918
     * @param callable $callable
3919
     * @param mixed    $arguments
3920
     *
3921
     * @return static
3922
     *                <p>(Immutable)</p>
3923
     *
3924
     * @phpstan-param  callable(T,mixed=):mixed $callable
3925
     * @phpstan-return static|static
3926
     * @psalm-mutation-free
3927
     */
3928
    public function invoke($callable, $arguments = []): self
3929
    {
3930
        // If one argument given for each iteration, create an array for it.
3931
        if (!\is_array($arguments)) {
7 ✔
3932
            $arguments = \array_fill(
7 ✔
3933
                0,
7 ✔
3934
                $this->count(),
7 ✔
3935
                $arguments
7 ✔
3936
            );
7 ✔
3937
        }
3938

3939
        // If the callable has arguments, pass them.
3940
        if ($arguments) {
7 ✔
3941
            $array = \array_map($callable, $this->toArray(), $arguments);
7 ✔
3942
        } else {
3943
            $array = $this->map($callable);
7 ✔
3944
        }
3945

3946
        return static::create(
7 ✔
3947
            /* @phpstan-ignore-next-line argument.type */
3948
            $array,
7 ✔
3949
            $this->iteratorClass,
7 ✔
3950
            false
7 ✔
3951
        );
7 ✔
3952
    }
3953

3954
    /**
3955
     * Check whether array is associative or not.
3956
     *
3957
     * EXAMPLE: <code>
3958
     * a(['foo' => 'bar', 2, 3])->isAssoc(); // true
3959
     * </code>
3960
     *
3961
     * @param bool $recursive
3962
     *
3963
     * @return bool
3964
     *              <p>Returns true if associative, false otherwise.</p>
3965
     */
3966
    public function isAssoc(bool $recursive = false): bool
3967
    {
3968
        if ($this->isEmpty()) {
105 ✔
3969
            return false;
21 ✔
3970
        }
3971

3972
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
3973
        foreach ($this->keys($recursive)->getGeneratorByReference() as &$key) {
91 ✔
3974
            if ((string) $key !== $key) {
91 ✔
3975
                return false;
77 ✔
3976
            }
3977
        }
3978

3979
        return true;
21 ✔
3980
    }
3981

3982
    /**
3983
     * Check if a given key or keys are empty.
3984
     *
3985
     * @param int|int[]|string|string[]|null $keys
3986
     *
3987
     * @return bool
3988
     *              <p>Returns true if empty, false otherwise.</p>
3989
     * @psalm-mutation-free
3990
     */
3991
    public function isEmpty($keys = null): bool
3992
    {
3993
        if ($this->generator) {
315 ✔
3994
            return $this->toArray() === [];
×
3995
        }
3996

3997
        if ($keys === null) {
315 ✔
3998
            return $this->array === [];
301 ✔
3999
        }
4000

4001
        foreach ((array) $keys as $key) {
14 ✔
4002
            if (!empty($this->get($key))) {
14 ✔
4003
                return false;
14 ✔
4004
            }
4005
        }
4006

4007
        return true;
14 ✔
4008
    }
4009

4010
    /**
4011
     * Check if the current array is equal to the given "$array" or not.
4012
     *
4013
     * EXAMPLE: <code>
4014
     * a(['πŸ’©'])->isEqual(['πŸ’©']); // true
4015
     * </code>
4016
     *
4017
     * @param array $array
4018
     *
4019
     * @return bool
4020
     *
4021
     * @phpstan-param array<TKey,T> $array
4022
     */
4023
    public function isEqual(array $array): bool
4024
    {
4025
        return $this->toArray() === $array;
7 ✔
4026
    }
4027

4028
    /**
4029
     * Check if the current array is a multi-array.
4030
     *
4031
     * EXAMPLE: <code>
4032
     * a(['foo' => [1, 2 , 3]])->isMultiArray(); // true
4033
     * </code>
4034
     *
4035
     * @return bool
4036
     */
4037
    public function isMultiArray(): bool
4038
    {
4039
        foreach ($this->getGenerator() as $value) {
154 ✔
4040
            if (\is_array($value)) {
140 ✔
4041
                return true;
35 ✔
4042
            }
4043
        }
4044

4045
        return false;
126 ✔
4046
    }
4047

4048
    /**
4049
     * Check whether array is numeric or not.
4050
     *
4051
     * @return bool
4052
     *              <p>Returns true if numeric, false otherwise.</p>
4053
     */
4054
    public function isNumeric(): bool
4055
    {
4056
        if ($this->isEmpty()) {
35 ✔
4057
            return false;
14 ✔
4058
        }
4059

4060
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4061
        foreach ($this->keys()->getGeneratorByReference() as &$key) {
28 ✔
4062
            if ((int) $key !== $key) {
28 ✔
4063
                return false;
14 ✔
4064
            }
4065
        }
4066

4067
        return true;
14 ✔
4068
    }
4069

4070
    /**
4071
     * Check if the current array is sequential [0, 1, 2, 3, 4, 5 ...] or not.
4072
     *
4073
     * EXAMPLE: <code>
4074
     * a([0 => 'foo', 1 => 'lall', 2 => 'foobar'])->isSequential(); // true
4075
     * </code>
4076
     *
4077
     * INFO: If the array is empty we count it as non-sequential.
4078
     *
4079
     * @param bool $recursive
4080
     *
4081
     * @return bool
4082
     * @psalm-mutation-free
4083
     */
4084
    public function isSequential(bool $recursive = false): bool
4085
    {
4086
        $i = 0;
70 ✔
4087
        foreach ($this->getGenerator() as $key => $value) {
70 ✔
4088
            if (
4089
                $recursive
63 ✔
4090
                &&
4091
                (\is_array($value) || $value instanceof \Traversable)
63 ✔
4092
                &&
4093
                /* @phpstan-ignore-next-line argument.type */
4094
                self::create($value)->isSequential() === false
63 ✔
4095
            ) {
4096
                return false;
7 ✔
4097
            }
4098

4099
            if ($key !== $i) {
63 ✔
4100
                return false;
21 ✔
4101
            }
4102

4103
            ++$i;
56 ✔
4104
        }
4105

4106
        return !($i === 0);
63 ✔
4107
    }
4108

4109
    /**
4110
     * @return array
4111
     *
4112
     * @phpstan-return array<TKey,T>
4113
     */
4114
    public function jsonSerialize(): array
4115
    {
4116
        /** @var array<TKey,T> $return */
4117
        $return = $this->toArray();
14 ✔
4118

4119
        return $return;
14 ✔
4120
    }
4121

4122
    /**
4123
     * Gets the key/index of the element at the current internal iterator position.
4124
     *
4125
     * @return int|string|null
4126
     * @phpstan-return array-key|null
4127
     */
4128
    public function key()
4129
    {
4130
        if ($this->generator) {
×
4131
            return $this->generator->key();
×
4132
        }
4133

4134
        return \key($this->array);
×
4135
    }
4136

4137
    /**
4138
     * Checks if the given key exists in the provided array.
4139
     *
4140
     * INFO: This method only use "array_key_exists()" if you want to use "dot"-notation,
4141
     *       then you need to use "Arrayy->offsetExists()".
4142
     *
4143
     * @param int|string $key the key to look for
4144
     *
4145
     * @return bool
4146
     * @psalm-mutation-free
4147
     */
4148
    public function keyExists($key): bool
4149
    {
4150
        foreach ($this->getGenerator() as $keyTmp => $value) {
1,468 ✔
4151
            if ($key === $keyTmp) {
1,433 ✔
4152
                return true;
1,321 ✔
4153
            }
4154
        }
4155

4156
        return false;
1,036 ✔
4157
    }
4158

4159
    /**
4160
     * Get all keys from the current array.
4161
     *
4162
     * EXAMPLE: <code>
4163
     * a([1 => 'foo', 2 => 'foo2', 3 => 'bar'])->keys(); // Arrayy[1, 2, 3]
4164
     * </code>
4165
     *
4166
     * @param bool       $recursive
4167
     *                                  [optional] <p>
4168
     *                                  Get all keys, also from all sub-arrays from an multi-dimensional array.
4169
     *                                  </p>
4170
     * @param mixed|null $search_values
4171
     *                                  [optional] <p>
4172
     *                                  If specified, then only keys containing these values are returned.
4173
     *                                  </p>
4174
     * @param bool       $strict
4175
     *                                  [optional] <p>
4176
     *                                  Determines if strict comparison (===) should be used during the search.
4177
     *                                  </p>
4178
     *
4179
     * @return static
4180
     *                <p>(Immutable) An array of all the keys in input.</p>
4181
     *
4182
     * @phpstan-param null|T|T[] $search_values
4183
     * @phpstan-return static
4184
     *
4185
     * @psalm-mutation-free
4186
     */
4187
    public function keys(
4188
        bool $recursive = false,
4189
        $search_values = null,
4190
        bool $strict = true
4191
    ): self {
4192
        // recursive
4193

4194
        if ($recursive === true) {
210 ✔
4195
            $array = $this->array_keys_recursive(
28 ✔
4196
                null,
28 ✔
4197
                $search_values,
28 ✔
4198
                $strict
28 ✔
4199
            );
28 ✔
4200

4201
            return static::create(
28 ✔
4202
                /* @phpstan-ignore-next-line argument.type */
4203
                $array,
28 ✔
4204
                $this->iteratorClass,
28 ✔
4205
                false
28 ✔
4206
            );
28 ✔
4207
        }
4208

4209
        // non recursive
4210

4211
        if ($search_values === null) {
203 ✔
4212
            $arrayFunction = function (): \Generator {
203 ✔
4213
                foreach ($this->getGenerator() as $key => $value) {
203 ✔
4214
                    yield $key;
189 ✔
4215
                }
4216
            };
203 ✔
4217
        } else {
4218
            $arrayFunction = function () use ($search_values, $strict): \Generator {
7 ✔
4219
                $is_array_tmp = \is_array($search_values);
7 ✔
4220

4221
                /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4222
                foreach ($this->getGeneratorByReference() as $key => &$value) {
7 ✔
4223
                    if (
4224
                        (
4225
                            $is_array_tmp === false
7 ✔
4226
                            &&
7 ✔
4227
                            $strict === true
7 ✔
4228
                            &&
7 ✔
4229
                            $search_values === $value
7 ✔
4230
                        )
4231
                        ||
4232
                        (
4233
                            $is_array_tmp === false
7 ✔
4234
                            &&
7 ✔
4235
                            $strict === false
7 ✔
4236
                            &&
7 ✔
4237
                            $search_values == $value
7 ✔
4238
                        )
4239
                        ||
4240
                        (
4241
                            $is_array_tmp === true
7 ✔
4242
                            &&
7 ✔
4243
                            \in_array($value, $search_values, $strict)
7 ✔
4244
                        )
4245
                    ) {
4246
                        yield $key;
7 ✔
4247
                    }
4248
                }
4249
            };
7 ✔
4250
        }
4251

4252
        return static::create(
203 ✔
4253
            $arrayFunction,
203 ✔
4254
            $this->iteratorClass,
203 ✔
4255
            false
203 ✔
4256
        );
203 ✔
4257
    }
4258

4259
    /**
4260
     * Sort an array by key in reverse order.
4261
     *
4262
     * @param int $sort_flags [optional] <p>
4263
     *                        You may modify the behavior of the sort using the optional
4264
     *                        parameter sort_flags, for details
4265
     *                        see sort.
4266
     *                        </p>
4267
     *
4268
     * @return $this
4269
     *               <p>(Mutable) Return this Arrayy object.</p>
4270
     *
4271
     * @phpstan-return static
4272
     */
4273
    public function krsort(int $sort_flags = 0): self
4274
    {
4275
        $this->generatorToArray();
28 ✔
4276

4277
        \krsort($this->array, $sort_flags);
28 ✔
4278

4279
        return $this;
28 ✔
4280
    }
4281

4282
    /**
4283
     * Sort an array by key in reverse order.
4284
     *
4285
     * @param int $sort_flags [optional] <p>
4286
     *                        You may modify the behavior of the sort using the optional
4287
     *                        parameter sort_flags, for details
4288
     *                        see sort.
4289
     *                        </p>
4290
     *
4291
     * @return $this
4292
     *               <p>(Immutable)</p>
4293
     *
4294
     * @phpstan-return static
4295
     * @psalm-mutation-free
4296
     */
4297
    public function krsortImmutable(int $sort_flags = 0): self
4298
    {
4299
        $that = clone $this;
28 ✔
4300

4301
        /**
4302
         * @psalm-suppress ImpureMethodCall - object is already cloned
4303
         */
4304
        $that->krsort($sort_flags);
28 ✔
4305

4306
        return $that;
28 ✔
4307
    }
4308

4309
    /**
4310
     * Get the last value from the current array.
4311
     *
4312
     * EXAMPLE: <code>
4313
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->last(); // 'lall'
4314
     * </code>
4315
     *
4316
     * @return mixed|null
4317
     *                    <p>Return null if there wasn't a element.</p>
4318
     *
4319
     * @phpstan-return T|null
4320
     * @psalm-mutation-free
4321
     */
4322
    public function last()
4323
    {
4324
        $key_last = $this->lastKey();
119 ✔
4325
        if ($key_last === null) {
119 ✔
4326
            return null;
14 ✔
4327
        }
4328

4329
        /** @var T $value_last */
4330
        $value_last = $this->get($key_last);
105 ✔
4331

4332
        return $value_last;
105 ✔
4333
    }
4334

4335
    /**
4336
     * Get the last key from the current array.
4337
     *
4338
     * @return mixed|null
4339
     *                    <p>Return null if there wasn't a element.</p>
4340
     *
4341
     * @phpstan-return null|TKey
4342
     * @psalm-mutation-free
4343
     */
4344
    public function lastKey()
4345
    {
4346
        $this->generatorToArray();
147 ✔
4347

4348
        return \array_key_last($this->array);
147 ✔
4349
    }
4350

4351
    /**
4352
     * Get the last value(s) from the current array.
4353
     *
4354
     * EXAMPLE: <code>
4355
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->lasts(2); // Arrayy[0 => 'bar', 1 => 'lall']
4356
     * </code>
4357
     *
4358
     * @param int|null $number
4359
     *
4360
     * @return static
4361
     *                <p>(Immutable)</p>
4362
     *
4363
     * @phpstan-return static
4364
     * @psalm-mutation-free
4365
     */
4366
    public function lastsImmutable(?int $number = null): self
4367
    {
4368
        if ($this->isEmpty()) {
91 ✔
4369
            return static::create(
7 ✔
4370
                /* @phpstan-ignore-next-line argument.type */
4371
                [],
7 ✔
4372
                $this->iteratorClass,
7 ✔
4373
                false
7 ✔
4374
            );
7 ✔
4375
        }
4376

4377
        if ($number === null) {
84 ✔
4378
            $poppedValue = $this->last();
56 ✔
4379

4380
            if ($poppedValue === null) {
56 ✔
4381
                $poppedValue = [$poppedValue];
7 ✔
4382
            } else {
4383
                $poppedValue = (array) $poppedValue;
49 ✔
4384
            }
4385

4386
            $arrayy = static::create(
56 ✔
4387
                /* @phpstan-ignore-next-line argument.type */
4388
                $poppedValue,
56 ✔
4389
                $this->iteratorClass,
56 ✔
4390
                false
56 ✔
4391
            );
56 ✔
4392
        } else {
4393
            $arrayy = $this->rest(-$number);
28 ✔
4394
        }
4395

4396
        return $arrayy;
84 ✔
4397
    }
4398

4399
    /**
4400
     * Get the last value(s) from the current array.
4401
     *
4402
     * EXAMPLE: <code>
4403
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->lasts(2); // Arrayy[0 => 'bar', 1 => 'lall']
4404
     * </code>
4405
     *
4406
     * @param int|null $number
4407
     *
4408
     * @return $this
4409
     *               <p>(Mutable)</p>
4410
     *
4411
     * @phpstan-return static
4412
     */
4413
    public function lastsMutable(?int $number = null): self
4414
    {
4415
        if ($this->isEmpty()) {
91 ✔
4416
            return $this;
7 ✔
4417
        }
4418

4419
        $this->array = $this->lastsImmutable($number)->toArray();
84 ✔
4420
        $this->generator = null;
84 ✔
4421

4422
        return $this;
84 ✔
4423
    }
4424

4425
    /**
4426
     * Count the values from the current array.
4427
     *
4428
     * alias: for "Arrayy->count()"
4429
     *
4430
     * @param int $mode
4431
     *
4432
     * @return int
4433
     *
4434
     * @see Arrayy::count()
4435
     */
4436
    public function length(int $mode = \COUNT_NORMAL): int
4437
    {
4438
        return $this->count($mode);
140 ✔
4439
    }
4440

4441
    /**
4442
     * Apply the given function to the every element of the array,
4443
     * collecting the results.
4444
     *
4445
     * EXAMPLE: <code>
4446
     * a(['foo', 'Foo'])->map('mb_strtoupper'); // Arrayy['FOO', 'FOO']
4447
     * </code>
4448
     *
4449
     * @param callable $callable
4450
     * @param bool     $useKeyAsSecondParameter
4451
     * @param mixed    ...$arguments
4452
     *
4453
     * @return static
4454
     *                <p>(Immutable) Arrayy object with modified elements.</p>
4455
     *
4456
     * @template T2
4457
     *              <p>The output value type.</p>
4458
     *
4459
     * @phpstan-param callable(T,TKey=,mixed=):T2 $callable
4460
     * @phpstan-return static<TKey,T2,array<TKey,T2>>
4461
     * @psalm-mutation-free
4462
     */
4463
    public function map(
4464
        callable $callable,
4465
        bool $useKeyAsSecondParameter = false,
4466
        ...$arguments
4467
    ) {
4468
        /**
4469
         * @psalm-suppress ImpureFunctionCall - func_num_args is only used to detect the number of args
4470
         */
4471
        $useArguments = \func_num_args() > 2;
56 ✔
4472

4473
        return static::create(
56 ✔
4474
            function () use ($useArguments, $callable, $useKeyAsSecondParameter, $arguments) {
56 ✔
4475
                foreach ($this->getGenerator() as $key => $value) {
56 ✔
4476
                    if ($useArguments) {
49 ✔
4477
                        if ($useKeyAsSecondParameter) {
21 ✔
4478
                            yield $key => $callable($value, $key, ...$arguments);
×
4479
                        } else {
4480
                            yield $key => $callable($value, ...$arguments);
21 ✔
4481
                        }
4482
                    } else {
4483
                        /** @noinspection NestedPositiveIfStatementsInspection */
4484
                        if ($useKeyAsSecondParameter) {
49 ✔
4485
                            yield $key => $callable($value, $key);
7 ✔
4486
                        } else {
4487
                            yield $key => $callable($value);
42 ✔
4488
                        }
4489
                    }
4490
                }
4491
            },
56 ✔
4492
            $this->iteratorClass,
56 ✔
4493
            false
56 ✔
4494
        );
56 ✔
4495
    }
4496

4497
    /**
4498
     * Check if all items in current array match a truth test.
4499
     *
4500
     * EXAMPLE: <code>
4501
     * $closure = function ($value, $key) {
4502
     *     return ($value % 2 === 0);
4503
     * };
4504
     * a([2, 4, 8])->matches($closure); // true
4505
     * </code>
4506
     *
4507
     * @param \Closure $closure
4508
     *
4509
     * @return bool
4510
     *
4511
     * @phpstan-param \Closure(T,TKey):bool $closure
4512
     */
4513
    public function matches(\Closure $closure): bool
4514
    {
4515
        if ($this->count() === 0) {
105 ✔
4516
            return false;
14 ✔
4517
        }
4518

4519
        foreach ($this->getGenerator() as $key => $value) {
91 ✔
4520
            $value = $closure($value, $key);
91 ✔
4521

4522
            if ($value === false) {
91 ✔
4523
                return false;
49 ✔
4524
            }
4525
        }
4526

4527
        return true;
49 ✔
4528
    }
4529

4530
    /**
4531
     * Check if any item in the current array matches a truth test.
4532
     *
4533
     * EXAMPLE: <code>
4534
     * $closure = function ($value, $key) {
4535
     *     return ($value % 2 === 0);
4536
     * };
4537
     * a([1, 4, 7])->matches($closure); // true
4538
     * </code>
4539
     *
4540
     * @param \Closure $closure
4541
     *
4542
     * @return bool
4543
     *
4544
     * @phpstan-param \Closure(T,TKey):bool $closure
4545
     */
4546
    public function matchesAny(\Closure $closure): bool
4547
    {
4548
        if ($this->count() === 0) {
98 ✔
4549
            return false;
14 ✔
4550
        }
4551

4552
        foreach ($this->getGenerator() as $key => $value) {
84 ✔
4553
            $value = $closure($value, $key);
84 ✔
4554

4555
            if ($value === true) {
84 ✔
4556
                return true;
63 ✔
4557
            }
4558
        }
4559

4560
        return false;
28 ✔
4561
    }
4562

4563
    /**
4564
     * Get the max value from an array.
4565
     *
4566
     * EXAMPLE: <code>
4567
     * a([-9, -8, -7, 1.32])->max(); // 1.32
4568
     * </code>
4569
     *
4570
     * @return false|float|int|string
4571
     *                                <p>Will return false if there are no values.</p>
4572
     */
4573
    public function max()
4574
    {
4575
        if ($this->count() === 0) {
77 ✔
4576
            return false;
7 ✔
4577
        }
4578

4579
        $max = false;
70 ✔
4580
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4581
        foreach ($this->getGeneratorByReference() as &$value) {
70 ✔
4582
            if (
4583
                $max === false
70 ✔
4584
                ||
4585
                $value > $max
70 ✔
4586
            ) {
4587
                $max = $value;
70 ✔
4588
            }
4589
        }
4590

4591
        return $max;
70 ✔
4592
    }
4593

4594
    /**
4595
     * Merge the new $array into the current array.
4596
     *
4597
     * - keep key,value from the current array, also if the index is in the new $array
4598
     *
4599
     * EXAMPLE: <code>
4600
     * $array1 = [1 => 'one', 'foo' => 'bar1'];
4601
     * $array2 = ['foo' => 'bar2', 3 => 'three'];
4602
     * a($array1)->mergeAppendKeepIndex($array2); // Arrayy[1 => 'one', 'foo' => 'bar2', 3 => 'three']
4603
     * // ---
4604
     * $array1 = [0 => 'one', 1 => 'foo'];
4605
     * $array2 = [0 => 'foo', 1 => 'bar2'];
4606
     * a($array1)->mergeAppendKeepIndex($array2); // Arrayy[0 => 'foo', 1 => 'bar2']
4607
     * </code>
4608
     *
4609
     * @param array $array
4610
     * @param bool  $recursive
4611
     *
4612
     * @return static
4613
     *                <p>(Immutable)</p>
4614
     *
4615
     * @phpstan-param  array<int|TKey,T> $array
4616
     * @phpstan-return static
4617
     * @psalm-mutation-free
4618
     */
4619
    public function mergeAppendKeepIndex(array $array = [], bool $recursive = false): self
4620
    {
4621
        if ($recursive === true) {
231 ✔
4622
            $array = $this->getArrayRecursiveHelperArrayy($array);
63 ✔
4623
            $result = \array_replace_recursive($this->toArray(), $array);
63 ✔
4624
        } else {
4625
            $result = \array_replace($this->toArray(), $array);
168 ✔
4626
        }
4627

4628
        return static::create(
231 ✔
4629
            /* @phpstan-ignore-next-line argument.type */
4630
            $result,
231 ✔
4631
            $this->iteratorClass,
231 ✔
4632
            false
231 ✔
4633
        );
231 ✔
4634
    }
4635

4636
    /**
4637
     * Merge the new $array into the current array.
4638
     *
4639
     * - replace duplicate assoc-keys from the current array with the key,values from the new $array
4640
     * - create new indexes
4641
     *
4642
     * EXAMPLE: <code>
4643
     * $array1 = [1 => 'one', 'foo' => 'bar1'];
4644
     * $array2 = ['foo' => 'bar2', 3 => 'three'];
4645
     * a($array1)->mergeAppendNewIndex($array2); // Arrayy[0 => 'one', 'foo' => 'bar2', 1 => 'three']
4646
     * // ---
4647
     * $array1 = [0 => 'one', 1 => 'foo'];
4648
     * $array2 = [0 => 'foo', 1 => 'bar2'];
4649
     * a($array1)->mergeAppendNewIndex($array2); // Arrayy[0 => 'one', 1 => 'foo', 2 => 'foo', 3 => 'bar2']
4650
     * </code>
4651
     *
4652
     * @param array $array
4653
     * @param bool  $recursive
4654
     *
4655
     * @return static
4656
     *                <p>(Immutable)</p>
4657
     *
4658
     * @phpstan-param  array<TKey,T> $array
4659
     * @phpstan-return static
4660
     * @psalm-mutation-free
4661
     */
4662
    public function mergeAppendNewIndex(array $array = [], bool $recursive = false): self
4663
    {
4664
        if ($recursive === true) {
140 ✔
4665
            $array = $this->getArrayRecursiveHelperArrayy($array);
35 ✔
4666
            $result = \array_merge_recursive($this->toArray(), $array);
35 ✔
4667
        } else {
4668
            $result = \array_merge($this->toArray(), $array);
105 ✔
4669
        }
4670

4671
        return static::create(
140 ✔
4672
            /* @phpstan-ignore-next-line argument.type */
4673
            $result,
140 ✔
4674
            $this->iteratorClass,
140 ✔
4675
            false
140 ✔
4676
        );
140 ✔
4677
    }
4678

4679
    /**
4680
     * Merge the the current array into the $array.
4681
     *
4682
     * - use key,value from the new $array, also if the index is in the current array
4683
     *
4684
     * EXAMPLE: <code>
4685
     * $array1 = [1 => 'one', 'foo' => 'bar1'];
4686
     * $array2 = ['foo' => 'bar2', 3 => 'three'];
4687
     * a($array1)->mergePrependKeepIndex($array2); // Arrayy['foo' => 'bar1', 3 => 'three', 1 => 'one']
4688
     * // ---
4689
     * $array1 = [0 => 'one', 1 => 'foo'];
4690
     * $array2 = [0 => 'foo', 1 => 'bar2'];
4691
     * a($array1)->mergePrependKeepIndex($array2); // Arrayy[0 => 'one', 1 => 'foo']
4692
     * </code>
4693
     *
4694
     * @param array $array
4695
     * @param bool  $recursive
4696
     *
4697
     * @return static
4698
     *                <p>(Immutable)</p>
4699
     *
4700
     * @phpstan-param  array<TKey,T> $array
4701
     * @phpstan-return static
4702
     * @psalm-mutation-free
4703
     */
4704
    public function mergePrependKeepIndex(array $array = [], bool $recursive = false): self
4705
    {
4706
        if ($recursive === true) {
119 ✔
4707
            $array = $this->getArrayRecursiveHelperArrayy($array);
28 ✔
4708
            $result = \array_replace_recursive($array, $this->toArray());
28 ✔
4709
        } else {
4710
            $result = \array_replace($array, $this->toArray());
91 ✔
4711
        }
4712

4713
        return static::create(
119 ✔
4714
            /* @phpstan-ignore-next-line argument.type */
4715
            $result,
119 ✔
4716
            $this->iteratorClass,
119 ✔
4717
            false
119 ✔
4718
        );
119 ✔
4719
    }
4720

4721
    /**
4722
     * Merge the current array into the new $array.
4723
     *
4724
     * - replace duplicate assoc-keys from new $array with the key,values from the current array
4725
     * - create new indexes
4726
     *
4727
     * EXAMPLE: <code>
4728
     * $array1 = [1 => 'one', 'foo' => 'bar1'];
4729
     * $array2 = ['foo' => 'bar2', 3 => 'three'];
4730
     * a($array1)->mergePrependNewIndex($array2); // Arrayy['foo' => 'bar1', 0 => 'three', 1 => 'one']
4731
     * // ---
4732
     * $array1 = [0 => 'one', 1 => 'foo'];
4733
     * $array2 = [0 => 'foo', 1 => 'bar2'];
4734
     * a($array1)->mergePrependNewIndex($array2); // Arrayy[0 => 'foo', 1 => 'bar2', 2 => 'one', 3 => 'foo']
4735
     * </code>
4736
     *
4737
     * @param array $array
4738
     * @param bool  $recursive
4739
     *
4740
     * @return static
4741
     *                <p>(Immutable)</p>
4742
     *
4743
     * @phpstan-param  array<TKey,T> $array
4744
     * @phpstan-return static
4745
     * @psalm-mutation-free
4746
     */
4747
    public function mergePrependNewIndex(array $array = [], bool $recursive = false): self
4748
    {
4749
        if ($recursive === true) {
147 ✔
4750
            $array = $this->getArrayRecursiveHelperArrayy($array);
49 ✔
4751
            $result = \array_merge_recursive($array, $this->toArray());
49 ✔
4752
        } else {
4753
            $result = \array_merge($array, $this->toArray());
98 ✔
4754
        }
4755

4756
        return static::create(
147 ✔
4757
            /* @phpstan-ignore-next-line argument.type */
4758
            $result,
147 ✔
4759
            $this->iteratorClass,
147 ✔
4760
            false
147 ✔
4761
        );
147 ✔
4762
    }
4763

4764
    /**
4765
     * Return a meta object with property names from phpdoc array-shape annotations,
4766
     * `@property` tags, and native declared properties.
4767
     *
4768
     * @return ArrayyMeta|mixed|static
4769
     */
4770
    public static function meta()
4771
    {
4772
        return (new ArrayyMeta())->getMetaObject(static::class);
320 ✔
4773
    }
4774

4775
    /**
4776
     * Get the min value from an array.
4777
     *
4778
     * EXAMPLE: <code>
4779
     * a([-9, -8, -7, 1.32])->min(); // -9
4780
     * </code>
4781
     *
4782
     * @return false|mixed
4783
     *                     <p>Will return false if there are no values.</p>
4784
     *
4785
     * @phpstan-return false|T
4786
     */
4787
    public function min()
4788
    {
4789
        if ($this->count() === 0) {
77 ✔
4790
            return false;
7 ✔
4791
        }
4792

4793
        $min = false;
70 ✔
4794
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4795
        foreach ($this->getGeneratorByReference() as &$value) {
70 ✔
4796
            if (
4797
                $min === false
70 ✔
4798
                ||
4799
                $value < $min
70 ✔
4800
            ) {
4801
                $min = $value;
70 ✔
4802
            }
4803
        }
4804

4805
        return $min;
70 ✔
4806
    }
4807

4808
    /**
4809
     * Get the most used value from the array.
4810
     *
4811
     * @return mixed|null
4812
     *                    <p>(Immutable) Return null if there wasn't an element.</p>
4813
     *
4814
     * @phpstan-return T|null
4815
     * @psalm-mutation-free
4816
     */
4817
    public function mostUsedValue()
4818
    {
4819
        /* @phpstan-ignore return.type */
4820
        return $this->countValues()->arsortImmutable()->firstKey();
21 ✔
4821
    }
4822

4823
    /**
4824
     * Get the most used value from the array.
4825
     *
4826
     * @param int|null $number <p>How many values you will take?</p>
4827
     *
4828
     * @return static
4829
     *                <p>(Immutable)</p>
4830
     *
4831
     * @phpstan-return static
4832
     * @psalm-mutation-free
4833
     */
4834
    public function mostUsedValues(?int $number = null): self
4835
    {
4836
        return $this->countValues()->arsortImmutable()->firstsKeys($number);
21 ✔
4837
    }
4838

4839
    /**
4840
     * Move an array element to a new index.
4841
     *
4842
     * EXAMPLE: <code>
4843
     * $arr2 = new A(['A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', 'E' => 'e']);
4844
     * $newArr2 = $arr2->moveElement('D', 1); // Arrayy['A' => 'a', 'D' => 'd', 'B' => 'b', 'C' => 'c', 'E' => 'e']
4845
     * </code>
4846
     *
4847
     * @param int|string $from
4848
     * @param int        $to
4849
     *
4850
     * @return static
4851
     *                <p>(Immutable)</p>
4852
     *
4853
     * @phpstan-return static
4854
     * @psalm-mutation-free
4855
     */
4856
    public function moveElement($from, $to): self
4857
    {
4858
        $array = $this->toArray();
7 ✔
4859

4860
        if ((int) $from === $from) {
7 ✔
4861
            $tmp = \array_splice($array, $from, 1);
7 ✔
4862
            \array_splice($array, (int) $to, 0, $tmp);
7 ✔
4863
            $output = $array;
7 ✔
4864
        } elseif ((string) $from === $from) {
7 ✔
4865
            $indexToMove = \array_search($from, \array_keys($array), true);
7 ✔
4866
            $itemToMove = $array[$from];
7 ✔
4867
            if ($indexToMove !== false) {
7 ✔
4868
                \array_splice($array, $indexToMove, 1);
7 ✔
4869
            }
4870
            $i = 0;
7 ✔
4871
            $output = [];
7 ✔
4872
            foreach ($array as $key => $item) {
7 ✔
4873
                if ($i === $to) {
7 ✔
4874
                    $output[$from] = $itemToMove;
7 ✔
4875
                }
4876
                $output[$key] = $item;
7 ✔
4877
                ++$i;
7 ✔
4878
            }
4879
        } else {
4880
            $output = [];
×
4881
        }
4882

4883
        return static::create(
7 ✔
4884
            /* @phpstan-ignore-next-line argument.type */
4885
            $output,
7 ✔
4886
            $this->iteratorClass,
7 ✔
4887
            false
7 ✔
4888
        );
7 ✔
4889
    }
4890

4891
    /**
4892
     * Move an array element to the first place.
4893
     *
4894
     * INFO: Instead of "Arrayy->moveElement()" this method will NOT
4895
     *       loss the keys of an indexed array.
4896
     *
4897
     * @param int|string $key
4898
     *
4899
     * @return static
4900
     *                <p>(Immutable)</p>
4901
     *
4902
     * @phpstan-return static
4903
     * @psalm-mutation-free
4904
     */
4905
    public function moveElementToFirstPlace($key): self
4906
    {
4907
        $array = $this->toArray();
7 ✔
4908

4909
        if ($this->offsetExists($key)) {
7 ✔
4910
            $tmpValue = $this->get($key);
7 ✔
4911
            unset($array[$key]);
7 ✔
4912
            $array = [$key => $tmpValue] + $array;
7 ✔
4913
        }
4914

4915
        return static::create(
7 ✔
4916
            /* @phpstan-ignore-next-line argument.type */
4917
            $array,
7 ✔
4918
            $this->iteratorClass,
7 ✔
4919
            false
7 ✔
4920
        );
7 ✔
4921
    }
4922

4923
    /**
4924
     * Move an array element to the last place.
4925
     *
4926
     * INFO: Instead of "Arrayy->moveElement()" this method will NOT
4927
     *       loss the keys of an indexed array.
4928
     *
4929
     * @param int|string $key
4930
     *
4931
     * @return static
4932
     *                <p>(Immutable)</p>
4933
     *
4934
     * @phpstan-return static
4935
     * @psalm-mutation-free
4936
     */
4937
    public function moveElementToLastPlace($key): self
4938
    {
4939
        $array = $this->toArray();
7 ✔
4940

4941
        if ($this->offsetExists($key)) {
7 ✔
4942
            $tmpValue = $this->get($key);
7 ✔
4943
            unset($array[$key]);
7 ✔
4944
            $array += [$key => $tmpValue];
7 ✔
4945
        }
4946

4947
        return static::create(
7 ✔
4948
            /* @phpstan-ignore-next-line argument.type */
4949
            $array,
7 ✔
4950
            $this->iteratorClass,
7 ✔
4951
            false
7 ✔
4952
        );
7 ✔
4953
    }
4954

4955
    /**
4956
     * Moves the internal iterator position to the next element and returns this element.
4957
     *
4958
     * @return false|mixed
4959
     *                     <p>(Mutable) Will return false if there are no values.</p>
4960
     *
4961
     * @phpstan-return false|T
4962
     */
4963
    public function next()
4964
    {
4965
        if ($this->generator) {
×
4966
            $this->generator->next();
×
4967

4968
            return $this->generator->current() ?? false;
×
4969
        }
4970

4971
        return \next($this->array);
×
4972
    }
4973

4974
    /**
4975
     * Get the next nth keys and values from the array.
4976
     *
4977
     * @param int $step
4978
     * @param int $offset
4979
     *
4980
     * @return static
4981
     *                <p>(Immutable)</p>
4982
     *
4983
     * @phpstan-return static
4984
     * @psalm-mutation-free
4985
     */
4986
    public function nth(int $step, int $offset = 0): self
4987
    {
4988
        $arrayFunction = function () use ($step, $offset): \Generator {
7 ✔
4989
            $position = 0;
7 ✔
4990
            foreach ($this->getGenerator() as $key => $value) {
7 ✔
4991
                if ($position++ % $step !== $offset) {
7 ✔
4992
                    continue;
7 ✔
4993
                }
4994

4995
                yield $key => $value;
7 ✔
4996
            }
4997
        };
7 ✔
4998

4999
        return static::create(
7 ✔
5000
            $arrayFunction,
7 ✔
5001
            $this->iteratorClass,
7 ✔
5002
            false
7 ✔
5003
        );
7 ✔
5004
    }
5005

5006
    /**
5007
     * Get a subset of the items from the given array.
5008
     *
5009
     * @param int[]|string[] $keys
5010
     *
5011
     * @return static
5012
     *                <p>(Immutable)</p>
5013
     *
5014
     * @phpstan-param array-key[] $keys
5015
     * @phpstan-return static
5016
     * @psalm-mutation-free
5017
     */
5018
    public function only(array $keys): self
5019
    {
5020
        $keys = \array_flip($keys);
7 ✔
5021

5022
        $generator = function () use ($keys): \Generator {
7 ✔
5023
            foreach ($this->getGenerator() as $key => $value) {
7 ✔
5024
                if (isset($keys[$key])) {
7 ✔
5025
                    yield $key => $value;
7 ✔
5026
                }
5027
            }
5028
        };
7 ✔
5029

5030
        return static::create(
7 ✔
5031
            $generator,
7 ✔
5032
            $this->iteratorClass,
7 ✔
5033
            false
7 ✔
5034
        );
7 ✔
5035
    }
5036

5037
    /**
5038
     * Pad array to the specified size with a given value.
5039
     *
5040
     * @param int   $size  <p>Size of the result array.</p>
5041
     * @param mixed $value <p>Empty value by default.</p>
5042
     *
5043
     * @return static
5044
     *                <p>(Immutable) Arrayy object padded to $size with $value.</p>
5045
     *
5046
     * @phpstan-return static
5047
     * @psalm-mutation-free
5048
     */
5049
    public function pad(int $size, $value): self
5050
    {
5051
        return static::create(
35 ✔
5052
            /* @phpstan-ignore-next-line argument.type */
5053
            \array_pad($this->toArray(), $size, $value),
35 ✔
5054
            $this->iteratorClass,
35 ✔
5055
            false
35 ✔
5056
        );
35 ✔
5057
    }
5058

5059
    /**
5060
     * Partitions this array in two array according to a predicate.
5061
     * Keys are preserved in the resulting array.
5062
     *
5063
     * @param \Closure $closure
5064
     *                          <p>The predicate on which to partition.</p>
5065
     *
5066
     * @return array<int, static>
5067
     *                    <p>An array with two elements. The first element contains the array
5068
     *                    of elements where the predicate returned TRUE, the second element
5069
     *                    contains the array of elements where the predicate returned FALSE.</p>
5070
     *
5071
     * @phpstan-param \Closure(T,TKey):bool $closure
5072
     * @phpstan-return array<int, static>
5073
     */
5074
    public function partition(\Closure $closure): array
5075
    {
5076
        // init
5077
        $matches = [];
7 ✔
5078
        $noMatches = [];
7 ✔
5079

5080
        foreach ($this->getGenerator() as $key => $value) {
7 ✔
5081
            if ($closure($value, $key)) {
7 ✔
5082
                $matches[$key] = $value;
7 ✔
5083
            } else {
5084
                $noMatches[$key] = $value;
7 ✔
5085
            }
5086
        }
5087

5088
        /* @phpstan-ignore-next-line argument.type */
5089
        return [self::create($matches), self::create($noMatches)];
7 ✔
5090
    }
5091

5092
    /**
5093
     * Pop a specified value off the end of the current array.
5094
     *
5095
     * @return mixed|null
5096
     *                    <p>(Mutable) The popped element from the current array or null if the array is e.g. empty.</p>
5097
     *
5098
     * @phpstan-return T|null
5099
     */
5100
    public function pop()
5101
    {
5102
        $this->generatorToArray();
35 ✔
5103

5104
        return \array_pop($this->array);
35 ✔
5105
    }
5106

5107
    /**
5108
     * Prepend a (key) + value to the current array.
5109
     *
5110
     * EXAMPLE: <code>
5111
     * a(['fΓ²Γ΄' => 'bΓ Ε™'])->prepend('foo'); // Arrayy[0 => 'foo', 'fΓ²Γ΄' => 'bΓ Ε™']
5112
     * </code>
5113
     *
5114
     * @param mixed $value
5115
     * @param mixed $key
5116
     *
5117
     * @return $this
5118
     *               <p>(Mutable) Return this Arrayy object, with the prepended value.</p>
5119
     *
5120
     * @phpstan-param T $value
5121
     * @phpstan-param TKey|null $key
5122
     * @phpstan-return static
5123
     */
5124
    public function prepend($value, $key = null)
5125
    {
5126
        $this->generatorToArray();
84 ✔
5127

5128
        if ($this->properties !== []) {
84 ✔
5129
            $this->checkType($key, $value);
28 ✔
5130
        }
5131

5132
        if ($key === null) {
70 ✔
5133
            \array_unshift($this->array, $value);
56 ✔
5134
        } else {
5135
            $this->array = [$key => $value] + $this->array; // @phpstan-ignore assign.propertyType
21 ✔
5136
        }
5137

5138
        return $this;
70 ✔
5139
    }
5140

5141
    /**
5142
     * Prepend a (key) + value to the current array.
5143
     *
5144
     * EXAMPLE: <code>
5145
     * a(['fΓ²Γ΄' => 'bΓ Ε™'])->prependImmutable('foo')->getArray(); // [0 => 'foo', 'fΓ²Γ΄' => 'bΓ Ε™']
5146
     * </code>
5147
     *
5148
     * @param mixed $value
5149
     * @param mixed $key
5150
     *
5151
     * @return $this
5152
     *               <p>(Immutable) Return this Arrayy object, with the prepended value.</p>
5153
     *
5154
     * @phpstan-param T $value
5155
     * @phpstan-param TKey $key
5156
     * @phpstan-return static
5157
     * @psalm-mutation-free
5158
     */
5159
    public function prependImmutable($value, $key = null)
5160
    {
5161
        $generator = function () use ($key, $value): \Generator {
7 ✔
5162
            if ($this->properties !== []) {
7 ✔
5163
                $this->checkType($key, $value);
×
5164
            }
5165

5166
            if ($key !== null) {
7 ✔
5167
                yield $key => $value;
×
5168
            } else {
5169
                yield $value;
7 ✔
5170
            }
5171

5172
            foreach ($this->getGenerator() as $keyOld => $itemOld) {
7 ✔
5173
                yield $keyOld => $itemOld;
7 ✔
5174
            }
5175
        };
7 ✔
5176

5177
        return static::create(
7 ✔
5178
            $generator,
7 ✔
5179
            $this->iteratorClass,
7 ✔
5180
            false
7 ✔
5181
        );
7 ✔
5182
    }
5183

5184
    /**
5185
     * Add a suffix to each key.
5186
     *
5187
     * @param float|int|string $suffix
5188
     *
5189
     * @return static
5190
     *                <p>(Immutable) Return an Arrayy object, with the prepended keys.</p>
5191
     *
5192
     * @phpstan-return static
5193
     * @psalm-mutation-free
5194
     */
5195
    public function prependToEachKey($suffix): self
5196
    {
5197
        // init
5198
        $result = [];
70 ✔
5199

5200
        foreach ($this->getGenerator() as $key => $item) {
70 ✔
5201
            if ($item instanceof self) {
63 ✔
5202
                $result[$key] = $item->prependToEachKey($suffix);
×
5203
            } elseif (\is_array($item)) {
63 ✔
5204
                $result[$key] = self::create(
×
5205
                    /* @phpstan-ignore-next-line argument.type */
5206
                    $item,
×
5207
                    $this->iteratorClass,
×
5208
                    false
×
5209
                )->prependToEachKey($suffix)
×
5210
                    ->toArray();
×
5211
            } else {
5212
                $result[$key . $suffix] = $item;
63 ✔
5213
            }
5214
        }
5215

5216
        return self::create(
70 ✔
5217
            /* @phpstan-ignore-next-line argument.type */
5218
            $result,
70 ✔
5219
            $this->iteratorClass,
70 ✔
5220
            false
70 ✔
5221
        );
70 ✔
5222
    }
5223

5224
    /**
5225
     * Add a suffix to each value.
5226
     *
5227
     * @param float|int|string $suffix
5228
     *
5229
     * @return static
5230
     *                <p>(Immutable) Return an Arrayy object, with the prepended values.</p>
5231
     *
5232
     * @phpstan-return static
5233
     * @psalm-mutation-free
5234
     */
5235
    public function prependToEachValue($suffix): self
5236
    {
5237
        // init
5238
        $result = [];
70 ✔
5239

5240
        foreach ($this->getGenerator() as $key => $item) {
70 ✔
5241
            if ($item instanceof self) {
63 ✔
5242
                $result[$key] = $item->prependToEachValue($suffix);
×
5243
            } elseif (\is_array($item)) {
63 ✔
5244
                $result[$key] = self::create(
×
5245
                    /* @phpstan-ignore-next-line argument.type */
5246
                    $item,
×
5247
                    $this->iteratorClass,
×
5248
                    false
×
5249
                )->prependToEachValue($suffix)
×
5250
                    ->toArray();
×
5251
            } elseif (\is_object($item) === true) {
63 ✔
5252
                $result[$key] = $item;
7 ✔
5253
            } else {
5254
                $result[$key] = $item . $suffix;
56 ✔
5255
            }
5256
        }
5257

5258
        return self::create(
70 ✔
5259
            /* @phpstan-ignore-next-line argument.type */
5260
            $result,
70 ✔
5261
            $this->iteratorClass,
70 ✔
5262
            false
70 ✔
5263
        );
70 ✔
5264
    }
5265

5266
    /**
5267
     * Return the value of a given key and
5268
     * delete the key.
5269
     *
5270
     * @param int|int[]|string|string[]|null $keyOrKeys
5271
     * @param mixed                          $fallback
5272
     *
5273
     * @return mixed
5274
     *
5275
     * @template TFallback $fallback
5276
     * @phpstan-param TFallback $fallback
5277
     * @phpstan-return TFallback|T|T[]
5278
     */
5279
    public function pull($keyOrKeys = null, $fallback = null)
5280
    {
5281
        if ($keyOrKeys === null) {
42 ✔
5282
            $array = $this->toArray();
7 ✔
5283
            $this->clear();
7 ✔
5284

5285
            return $array;
7 ✔
5286
        }
5287

5288
        if (\is_array($keyOrKeys)) {
35 ✔
5289
            $valueOrValues = [];
7 ✔
5290
            foreach ($keyOrKeys as $key) {
7 ✔
5291
                $valueOrValues[] = $this->get($key, $fallback);
7 ✔
5292
                $this->offsetUnset($key);
7 ✔
5293
            }
5294
        } else {
5295
            $valueOrValues = $this->get($keyOrKeys, $fallback);
35 ✔
5296
            $this->offsetUnset($keyOrKeys);
35 ✔
5297
        }
5298

5299
        /** @var T|T[]|TFallback $valueOrValues */
5300
        return $valueOrValues;
35 ✔
5301
    }
5302

5303
    /**
5304
     * Push one or more values onto the end of array at once.
5305
     *
5306
     * @param mixed ...$args
5307
     *
5308
     * @return $this
5309
     *               <p>(Mutable) Return this Arrayy object, with pushed elements to the end of array.</p>
5310
     *
5311
     * @noinspection ReturnTypeCanBeDeclaredInspection
5312
     *
5313
     * @phpstan-param  array<TKey,T> ...$args
5314
     * @phpstan-return static
5315
     */
5316
    public function push(...$args)
5317
    {
5318
        $this->generatorToArray();
63 ✔
5319

5320
        if (
5321
            $this->checkPropertyTypes
63 ✔
5322
            &&
5323
            $this->properties !== []
63 ✔
5324
        ) {
5325
            foreach ($args as $key => $value) {
21 ✔
5326
                $this->checkType($key, $value);
21 ✔
5327
            }
5328
        }
5329

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

5332
        return $this;
56 ✔
5333
    }
5334

5335
    /**
5336
     * Get a random value from the current array.
5337
     *
5338
     * EXAMPLE: <code>
5339
     * a([1, 2, 3, 4])->randomImmutable(2); // e.g.: Arrayy[1, 4]
5340
     * </code>
5341
     *
5342
     * @param int|null $number <p>How many values you will take?</p>
5343
     *
5344
     * @return static
5345
     *                <p>(Immutable)</p>
5346
     *
5347
     * @phpstan-return static
5348
     */
5349
    public function randomImmutable(?int $number = null): self
5350
    {
5351
        $this->generatorToArray();
133 ✔
5352

5353
        if ($this->count() === 0) {
133 ✔
5354
            return static::create(
7 ✔
5355
                /* @phpstan-ignore-next-line argument.type */
5356
                [],
7 ✔
5357
                $this->iteratorClass,
7 ✔
5358
                false
7 ✔
5359
            );
7 ✔
5360
        }
5361

5362
        if ($number === null) {
126 ✔
5363
            $arrayRandValue = [$this->array[\array_rand($this->array)]];
91 ✔
5364

5365
            return static::create(
91 ✔
5366
                /* @phpstan-ignore-next-line argument.type */
5367
                $arrayRandValue,
91 ✔
5368
                $this->iteratorClass,
91 ✔
5369
                false
91 ✔
5370
            );
91 ✔
5371
        }
5372

5373
        $arrayTmp = $this->array;
42 ✔
5374
        \shuffle($arrayTmp);
42 ✔
5375

5376
        return static::create(
42 ✔
5377
            /* @phpstan-ignore-next-line argument.type */
5378
            $arrayTmp,
42 ✔
5379
            $this->iteratorClass,
42 ✔
5380
            false
42 ✔
5381
        )->firstsImmutable($number);
42 ✔
5382
    }
5383

5384
    /**
5385
     * Pick a random key/index from the keys of this array.
5386
     *
5387
     * EXAMPLE: <code>
5388
     * $arrayy = A::create([1 => 'one', 2 => 'two']);
5389
     * $arrayy->randomKey(); // e.g. 2
5390
     * </code>
5391
     *
5392
     * @throws \RangeException If array is empty
5393
     *
5394
     * @return mixed|null
5395
     *                    <p>Get a key/index or null if there wasn't a key/index.</p>
5396
     *
5397
     * @phpstan-return null|TKey
5398
     */
5399
    public function randomKey()
5400
    {
5401
        $result = $this->randomKeys(1);
28 ✔
5402

5403
        if (!isset($result[0])) {
28 ✔
5404
            $result[0] = null;
×
5405
        }
5406

5407
        return $result[0];
28 ✔
5408
    }
5409

5410
    /**
5411
     * Pick a given number of random keys/indexes out of this array.
5412
     *
5413
     * EXAMPLE: <code>
5414
     * a([1 => 'one', 2 => 'two'])->randomKeys(); // e.g. Arrayy[1, 2]
5415
     * </code>
5416
     *
5417
     * @param int $number <p>The number of keys/indexes (should be <= \count($this->array))</p>
5418
     *
5419
     * @throws \RangeException If array is empty
5420
     *
5421
     * @return static
5422
     *                <p>(Immutable)</p>
5423
     *
5424
     * @phpstan-return static
5425
     */
5426
    public function randomKeys(int $number): self
5427
    {
5428
        $this->generatorToArray();
91 ✔
5429

5430
        $count = $this->count();
91 ✔
5431

5432
        if (
5433
            $number === 0
91 ✔
5434
            ||
5435
            $number > $count
91 ✔
5436
        ) {
5437
            throw new \RangeException(
14 ✔
5438
                \sprintf(
14 ✔
5439
                    'Number of requested keys (%s) must be equal or lower than number of elements in this array (%s)',
14 ✔
5440
                    $number,
14 ✔
5441
                    $count
14 ✔
5442
                )
14 ✔
5443
            );
14 ✔
5444
        }
5445

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

5448
        return static::create(
77 ✔
5449
            /* @phpstan-ignore-next-line argument.type */
5450
            $result,
77 ✔
5451
            $this->iteratorClass,
77 ✔
5452
            false
77 ✔
5453
        );
77 ✔
5454
    }
5455

5456
    /**
5457
     * Get a random value from the current array.
5458
     *
5459
     * EXAMPLE: <code>
5460
     * a([1, 2, 3, 4])->randomMutable(2); // e.g.: Arrayy[1, 4]
5461
     * </code>
5462
     *
5463
     * @param int|null $number <p>How many values you will take?</p>
5464
     *
5465
     * @return $this
5466
     *               <p>(Mutable) Return this Arrayy object.</p>
5467
     *
5468
     * @phpstan-return static
5469
     */
5470
    public function randomMutable(?int $number = null): self
5471
    {
5472
        $this->generatorToArray();
119 ✔
5473

5474
        if ($this->count() === 0) {
119 ✔
5475
            return static::create(
×
5476
                /* @phpstan-ignore-next-line argument.type */
5477
                [],
×
5478
                $this->iteratorClass,
×
5479
                false
×
5480
            );
×
5481
        }
5482

5483
        if ($number === null) {
119 ✔
5484
            $arrayRandValue = [$this->array[\array_rand($this->array)]];
49 ✔
5485
            $this->array = $arrayRandValue; // @phpstan-ignore assign.propertyType
49 ✔
5486

5487
            return $this;
49 ✔
5488
        }
5489

5490
        \shuffle($this->array);
77 ✔
5491

5492
        return $this->firstsMutable($number);
77 ✔
5493
    }
5494

5495
    /**
5496
     * Pick a random value from the values of this array.
5497
     *
5498
     * EXAMPLE: <code>
5499
     * a([1 => 'one', 2 => 'two'])->randomValue(); // e.g. 'one'
5500
     * </code>
5501
     *
5502
     * @return mixed
5503
     *               <p>Get a random value or null if there wasn't a value.</p>
5504
     *
5505
     * @phpstan-return T|null
5506
     */
5507
    public function randomValue()
5508
    {
5509
        $result = $this->randomImmutable();
28 ✔
5510

5511
        if (!isset($result[0])) {
28 ✔
5512
            $result[0] = null;
×
5513
        }
5514

5515
        return $result[0];
28 ✔
5516
    }
5517

5518
    /**
5519
     * Pick a given number of random values out of this array.
5520
     *
5521
     * EXAMPLE: <code>
5522
     * a([1 => 'one', 2 => 'two'])->randomValues(); // e.g. Arrayy['one', 'two']
5523
     * </code>
5524
     *
5525
     * @param int $number
5526
     *
5527
     * @return static
5528
     *                <p>(Mutable)</p>
5529
     *
5530
     * @phpstan-return static
5531
     */
5532
    public function randomValues(int $number): self
5533
    {
5534
        return $this->randomMutable($number);
49 ✔
5535
    }
5536

5537
    /**
5538
     * Get a random value from an array, with the ability to skew the results.
5539
     *
5540
     * EXAMPLE: <code>
5541
     * a([0 => 3, 1 => 4])->randomWeighted([1 => 4]); // e.g.: Arrayy[4] (has a 66% chance of returning 4)
5542
     * </code>
5543
     *
5544
     * @param array    $array
5545
     * @param int|null $number <p>How many values you will take?</p>
5546
     *
5547
     * @return static
5548
     *                           <p>(Immutable)</p>
5549
     *
5550
     * @phpstan-param  array<(int&T)|(string&T),int> $array
5551
     * @phpstan-return static
5552
     */
5553
    public function randomWeighted(array $array, ?int $number = null): self
5554
    {
5555
        // init
5556
        $options = [];
63 ✔
5557

5558
        foreach ($array as $option => $weight) {
63 ✔
5559
            if ($this->searchIndex($option) !== false) {
63 ✔
5560
                for ($i = 0; $i < $weight; ++$i) {
14 ✔
5561
                    $options[] = $option;
7 ✔
5562
                }
5563
            }
5564
        }
5565

5566
        return $this->mergeAppendKeepIndex($options)->randomImmutable($number);
63 ✔
5567
    }
5568

5569
    /**
5570
     * Reduce the current array via callable e.g. anonymous-function and return the end result.
5571
     *
5572
     * EXAMPLE: <code>
5573
     * a([1, 2, 3, 4])->reduce(
5574
     *     function ($carry, $item) {
5575
     *         return $carry * $item;
5576
     *     },
5577
     *     1
5578
     * ); // Arrayy[24]
5579
     * </code>
5580
     *
5581
     * @param callable $callable
5582
     * @param mixed    $initial
5583
     *
5584
     * @return static
5585
     *                <p>(Immutable)</p>
5586
     *
5587
     * @template T2
5588
     *              <p>The output value type.</p>
5589
     *
5590
     * @phpstan-param callable(T2, T, TKey): T2 $callable
5591
     * @phpstan-param T2                        $initial
5592
     *
5593
     * @phpstan-return static
5594
     * @psalm-mutation-free
5595
     */
5596
    public function reduce($callable, $initial = []): self
5597
    {
5598
        foreach ($this->getGenerator() as $key => $value) {
126 ✔
5599
            $initial = $callable($initial, $value, $key);
119 ✔
5600
        }
5601

5602
        /** @var static $return - help for phpstan */
5603
        $return = static::create(
126 ✔
5604
            $initial,
126 ✔
5605
            $this->iteratorClass,
126 ✔
5606
            false
126 ✔
5607
        );
126 ✔
5608

5609
        return $return;
126 ✔
5610
    }
5611

5612
    /**
5613
     * @param bool $unique
5614
     *
5615
     * @return static
5616
     *                <p>(Immutable)</p>
5617
     *
5618
     * @phpstan-return static
5619
     * @psalm-mutation-free
5620
     */
5621
    public function reduce_dimension(bool $unique = true): self
5622
    {
5623
        // init
5624
        $result = [];
98 ✔
5625

5626
        foreach ($this->getGenerator() as $val) {
98 ✔
5627
            if (\is_array($val)) {
84 ✔
5628
                /* @phpstan-ignore-next-line argument.type */
5629
                $result[] = static::create($val)->reduce_dimension($unique)->toArray();
35 ✔
5630
            } else {
5631
                $result[] = [$val];
84 ✔
5632
            }
5633
        }
5634

5635
        $result = $result === [] ? [] : \array_merge(...$result);
98 ✔
5636

5637
        /* @phpstan-ignore-next-line argument.type */
5638
        $resultArrayy = static::create($result);
98 ✔
5639

5640
        /**
5641
         * @psalm-suppress ImpureMethodCall - object is already re-created
5642
         * @psalm-suppress InvalidReturnStatement - why?
5643
         */
5644
        return $unique ? $resultArrayy->unique() : $resultArrayy;
98 ✔
5645
    }
5646

5647
    /**
5648
     * Create a numerically re-indexed Arrayy object.
5649
     *
5650
     * EXAMPLE: <code>
5651
     * a([2 => 1, 3 => 2])->reindex(); // Arrayy[0 => 1, 1 => 2]
5652
     * </code>
5653
     *
5654
     * @return $this
5655
     *               <p>(Mutable) Return this Arrayy object, with re-indexed array-elements.</p>
5656
     *
5657
     * @phpstan-return static
5658
     */
5659
    public function reindex(): self
5660
    {
5661
        $this->generatorToArray(false);
63 ✔
5662

5663
        $this->array = \array_values($this->array);
63 ✔
5664

5665
        return $this;
63 ✔
5666
    }
5667

5668
    /**
5669
     * Return all items that fail the truth test.
5670
     *
5671
     * EXAMPLE: <code>
5672
     * $closure = function ($value) {
5673
     *     return $value % 2 !== 0;
5674
     * }
5675
     * a([1, 2, 3, 4])->reject($closure); // Arrayy[1 => 2, 3 => 4]
5676
     * </code>
5677
     *
5678
     * @param \Closure $closure
5679
     *
5680
     * @return static
5681
     *                <p>(Immutable)</p>
5682
     *
5683
     * @phpstan-param \Closure(T,TKey):bool  $closure
5684
     * @phpstan-return static
5685
     * @psalm-mutation-free
5686
     */
5687
    public function reject(\Closure $closure): self
5688
    {
5689
        // init
5690
        $filtered = [];
7 ✔
5691

5692
        foreach ($this->getGenerator() as $key => $value) {
7 ✔
5693
            if (!$closure($value, $key)) {
7 ✔
5694
                $filtered[$key] = $value;
7 ✔
5695
            }
5696
        }
5697

5698
        return static::create(
7 ✔
5699
            /* @phpstan-ignore-next-line argument.type */
5700
            $filtered,
7 ✔
5701
            $this->iteratorClass,
7 ✔
5702
            false
7 ✔
5703
        );
7 ✔
5704
    }
5705

5706
    /**
5707
     * Remove a value from the current array (optional using dot-notation).
5708
     *
5709
     * EXAMPLE: <code>
5710
     * a([1 => 'bar', 'foo' => 'foo'])->remove(1); // Arrayy['foo' => 'foo']
5711
     * </code>
5712
     *
5713
     * @param mixed $key
5714
     *
5715
     * @return static
5716
     *                <p>(Mutable)</p>
5717
     *
5718
     * @phpstan-param  TKey|TKey[] $key
5719
     * @phpstan-return static
5720
     */
5721
    public function remove($key)
5722
    {
5723
        // recursive call
5724
        if (\is_array($key)) {
154 ✔
5725
            foreach ($key as $k) {
7 ✔
5726
                $this->internalRemove($k);
7 ✔
5727
            }
5728

5729
            return static::create(
7 ✔
5730
                /* @phpstan-ignore-next-line argument.type */
5731
                $this->toArray(),
7 ✔
5732
                $this->iteratorClass,
7 ✔
5733
                false
7 ✔
5734
            );
7 ✔
5735
        }
5736

5737
        $this->internalRemove($key);
147 ✔
5738

5739
        return static::create(
147 ✔
5740
            /* @phpstan-ignore-next-line argument.type */
5741
            $this->toArray(),
147 ✔
5742
            $this->iteratorClass,
147 ✔
5743
            false
147 ✔
5744
        );
147 ✔
5745
    }
5746

5747
    /**
5748
     * alias: for "Arrayy->removeValue()"
5749
     *
5750
     * @param mixed $element
5751
     *
5752
     * @return static
5753
     *                <p>(Immutable)</p>
5754
     *
5755
     * @phpstan-param  T $element
5756
     * @phpstan-return static
5757
     * @psalm-mutation-free
5758
     */
5759
    public function removeElement($element)
5760
    {
5761
        return $this->removeValue($element);
56 ✔
5762
    }
5763

5764
    /**
5765
     * Remove the first value from the current array.
5766
     *
5767
     * EXAMPLE: <code>
5768
     * a([1 => 'bar', 'foo' => 'foo'])->removeFirst(); // Arrayy['foo' => 'foo']
5769
     * </code>
5770
     *
5771
     * @return static
5772
     *                <p>(Immutable)</p>
5773
     *
5774
     * @phpstan-return static
5775
     * @psalm-mutation-free
5776
     */
5777
    public function removeFirst(): self
5778
    {
5779
        $tmpArray = $this->toArray();
49 ✔
5780

5781
        \array_shift($tmpArray);
49 ✔
5782

5783
        return static::create(
49 ✔
5784
            /* @phpstan-ignore-next-line argument.type */
5785
            $tmpArray,
49 ✔
5786
            $this->iteratorClass,
49 ✔
5787
            false
49 ✔
5788
        );
49 ✔
5789
    }
5790

5791
    /**
5792
     * Remove the last value from the current array.
5793
     *
5794
     * EXAMPLE: <code>
5795
     * a([1 => 'bar', 'foo' => 'foo'])->removeLast(); // Arrayy[1 => 'bar']
5796
     * </code>
5797
     *
5798
     * @return static
5799
     *                <p>(Immutable)</p>
5800
     *
5801
     * @phpstan-return static
5802
     * @psalm-mutation-free
5803
     */
5804
    public function removeLast(): self
5805
    {
5806
        $tmpArray = $this->toArray();
49 ✔
5807

5808
        \array_pop($tmpArray);
49 ✔
5809

5810
        return static::create(
49 ✔
5811
            /* @phpstan-ignore-next-line argument.type */
5812
            $tmpArray,
49 ✔
5813
            $this->iteratorClass,
49 ✔
5814
            false
49 ✔
5815
        );
49 ✔
5816
    }
5817

5818
    /**
5819
     * Removes a particular value from an array (numeric or associative).
5820
     *
5821
     * EXAMPLE: <code>
5822
     * a([1 => 'bar', 'foo' => 'foo'])->removeValue('foo'); // Arrayy[1 => 'bar']
5823
     * </code>
5824
     *
5825
     * @param mixed $value
5826
     *
5827
     * @return static
5828
     *                <p>(Immutable)</p>
5829
     *
5830
     * @phpstan-param  T $value
5831
     * @phpstan-return static
5832
     * @psalm-mutation-free
5833
     */
5834
    public function removeValue($value): self
5835
    {
5836
        $this->generatorToArray();
56 ✔
5837

5838
        // init
5839
        $isSequentialArray = $this->isSequential();
56 ✔
5840

5841
        foreach ($this->array as $key => $item) {
56 ✔
5842
            if ($item === $value) {
49 ✔
5843
                unset($this->array[$key]);
49 ✔
5844
            }
5845
        }
5846

5847
        if ($isSequentialArray) {
56 ✔
5848
            $this->array = \array_values($this->array);
42 ✔
5849
        }
5850

5851
        return static::create(
56 ✔
5852
            /* @phpstan-ignore-next-line argument.type */
5853
            $this->array,
56 ✔
5854
            $this->iteratorClass,
56 ✔
5855
            false
56 ✔
5856
        );
56 ✔
5857
    }
5858

5859
    /**
5860
     * Generate array of repeated arrays.
5861
     *
5862
     * @param int $times <p>How many times has to be repeated.</p>
5863
     *
5864
     * @return static
5865
     *                <p>(Immutable)</p>
5866
     *
5867
     * @phpstan-return static
5868
     * @psalm-mutation-free
5869
     */
5870
    public function repeat($times): self
5871
    {
5872
        if ($times === 0) {
7 ✔
5873
            /* @phpstan-ignore-next-line argument.type */
5874
            return static::create([], $this->iteratorClass);
7 ✔
5875
        }
5876

5877
        return static::create(
7 ✔
5878
            /* @phpstan-ignore-next-line argument.type */
5879
            \array_fill(0, (int) $times, $this->toArray()),
7 ✔
5880
            $this->iteratorClass,
7 ✔
5881
            false
7 ✔
5882
        );
7 ✔
5883
    }
5884

5885
    /**
5886
     * Replace a key with a new key/value pair.
5887
     *
5888
     * EXAMPLE: <code>
5889
     * $arrayy = a([1 => 'foo', 2 => 'foo2', 3 => 'bar']);
5890
     * $arrayy->replace(2, 'notfoo', 'notbar'); // Arrayy[1 => 'foo', 'notfoo' => 'notbar', 3 => 'bar']
5891
     * </code>
5892
     *
5893
     * @param mixed $oldKey
5894
     * @param mixed $newKey
5895
     * @param mixed $newValue
5896
     *
5897
     * @return static
5898
     *                <p>(Immutable)</p>
5899
     *
5900
     * @phpstan-param TKey $oldKey
5901
     * @phpstan-param TKey $newKey
5902
     * @phpstan-param T $newValue
5903
     * @phpstan-return static
5904
     * @psalm-mutation-free
5905
     */
5906
    public function replace($oldKey, $newKey, $newValue): self
5907
    {
5908
        $that = clone $this;
35 ✔
5909

5910
        /**
5911
         * @psalm-suppress ImpureMethodCall - object is already cloned
5912
         */
5913
        return $that->remove($oldKey)
35 ✔
5914
            ->set($newKey, $newValue);
35 ✔
5915
    }
5916

5917
    /**
5918
     * Create an array using the current array as values and the other array as keys.
5919
     *
5920
     * EXAMPLE: <code>
5921
     * $firstArray = [
5922
     *     1 => 'one',
5923
     *     2 => 'two',
5924
     *     3 => 'three',
5925
     * ];
5926
     * $secondArray = [
5927
     *     'one' => 1,
5928
     *     1     => 'one',
5929
     *     2     => 2,
5930
     * ];
5931
     * $arrayy = a($firstArray);
5932
     * $arrayy->replaceAllKeys($secondArray); // Arrayy[1 => "one", 'one' => "two", 2 => "three"]
5933
     * </code>
5934
     *
5935
     * @param int[]|string[] $keys <p>An array of keys.</p>
5936
     *
5937
     * @return static
5938
     *                <p>(Immutable) Arrayy object with keys from the other array, empty Arrayy object if the number of elements
5939
     *                for each array isn't equal or if the arrays are empty.
5940
     *                </p>
5941
     *
5942
     * @phpstan-param  array<TKey> $keys
5943
     * @phpstan-return static
5944
     * @psalm-mutation-free
5945
     */
5946
    public function replaceAllKeys(array $keys): self
5947
    {
5948
        $values = $this->toArray();
21 ✔
5949
        $data = \count($keys) === \count($values) ? \array_combine($keys, $values) : [];
21 ✔
5950

5951
        return static::create(
21 ✔
5952
            /* @phpstan-ignore-next-line argument.type */
5953
            $data,
21 ✔
5954
            $this->iteratorClass,
21 ✔
5955
            false
21 ✔
5956
        );
21 ✔
5957
    }
5958

5959
    /**
5960
     * Create an array using the current array as keys and the other array as values.
5961
     *
5962
     * EXAMPLE: <code>
5963
     * $firstArray = [
5964
     *     1 => 'one',
5965
     *     2 => 'two',
5966
     *     3 => 'three',
5967
     * ];
5968
     * $secondArray = [
5969
     *     'one' => 1,
5970
     *     1     => 'one',
5971
     *     2     => 2,
5972
     * ];
5973
     * $arrayy = a($firstArray);
5974
     * $arrayy->replaceAllValues($secondArray); // Arrayy['one' => 1, 'two' => 'one', 'three' => 2]
5975
     * </code>
5976
     *
5977
     * @param array $array <p>An array of values.</p>
5978
     *
5979
     * @return static
5980
     *                <p>(Immutable) Arrayy object with values from the other array, empty Arrayy object if the number of elements
5981
     *                for each array isn't equal or if the arrays are empty.
5982
     *                </p>
5983
     *
5984
     * @phpstan-param  array<T> $array
5985
     * @phpstan-return static
5986
     * @psalm-mutation-free
5987
     */
5988
    public function replaceAllValues(array $array): self
5989
    {
5990
        $keys = $this->toArray();
21 ✔
5991
        $data = \count($keys) === \count($array) ? \array_combine($keys, $array) : [];
21 ✔
5992

5993
        return static::create(
21 ✔
5994
            /* @phpstan-ignore-next-line argument.type */
5995
            $data,
21 ✔
5996
            $this->iteratorClass,
21 ✔
5997
            false
21 ✔
5998
        );
21 ✔
5999
    }
6000

6001
    /**
6002
     * Replace the keys in an array with another set.
6003
     *
6004
     * EXAMPLE: <code>
6005
     * a([1 => 'bar', 'foo' => 'foo'])->replaceKeys([1 => 2, 'foo' => 'replaced']); // Arrayy[2 => 'bar', 'replaced' => 'foo']
6006
     * </code>
6007
     *
6008
     * @param array $keys <p>An array of keys matching the array's size.</p>
6009
     *
6010
     * @return static
6011
     *                <p>(Immutable)</p>
6012
     *
6013
     * @phpstan-param  array<TKey> $keys
6014
     * @phpstan-return static
6015
     * @psalm-mutation-free
6016
     */
6017
    public function replaceKeys(array $keys): self
6018
    {
6019
        $values = \array_values($this->toArray());
14 ✔
6020
        $result = \count($keys) === \count($values) ? \array_combine($keys, $values) : [];
14 ✔
6021

6022
        return static::create(
14 ✔
6023
            /* @phpstan-ignore-next-line argument.type */
6024
            $result,
14 ✔
6025
            $this->iteratorClass,
14 ✔
6026
            false
14 ✔
6027
        );
14 ✔
6028
    }
6029

6030
    /**
6031
     * Replace the first matched value in an array.
6032
     *
6033
     * EXAMPLE: <code>
6034
     * $testArray = ['bar', 'foo' => 'foo', 'foobar' => 'foobar'];
6035
     * a($testArray)->replaceOneValue('foo', 'replaced'); // Arrayy['bar', 'foo' => 'replaced', 'foobar' => 'foobar']
6036
     * </code>
6037
     *
6038
     * @param mixed $search      <p>The value to replace.</p>
6039
     * @param mixed $replacement <p>The value to replace.</p>
6040
     *
6041
     * @return static
6042
     *                <p>(Immutable)</p>
6043
     *
6044
     * @phpstan-param T $search
6045
     * @phpstan-param T $replacement
6046
     * @phpstan-return static
6047
     * @psalm-mutation-free
6048
     */
6049
    public function replaceOneValue($search, $replacement = ''): self
6050
    {
6051
        $array = $this->toArray();
21 ✔
6052
        $key = \array_search($search, $array, true);
21 ✔
6053

6054
        if ($key !== false) {
21 ✔
6055
            $array[$key] = $replacement;
21 ✔
6056
        }
6057

6058
        return static::create(
21 ✔
6059
            /* @phpstan-ignore-next-line argument.type */
6060
            $array,
21 ✔
6061
            $this->iteratorClass,
21 ✔
6062
            false
21 ✔
6063
        );
21 ✔
6064
    }
6065

6066
    /**
6067
     * Replace values in the current array.
6068
     *
6069
     * EXAMPLE: <code>
6070
     * $testArray = ['bar', 'foo' => 'foo', 'foobar' => 'foobar'];
6071
     * a($testArray)->replaceValues('foo', 'replaced'); // Arrayy['bar', 'foo' => 'replaced', 'foobar' => 'replacedbar']
6072
     * </code>
6073
     *
6074
     * @param string $search      <p>The value to replace.</p>
6075
     * @param string $replacement <p>What to replace it with.</p>
6076
     *
6077
     * @return static
6078
     *                <p>(Immutable)</p>
6079
     *
6080
     * @phpstan-return static
6081
     * @psalm-mutation-free
6082
     */
6083
    public function replaceValues($search, $replacement = ''): self
6084
    {
6085
        $callable = static function ($value) use ($search, $replacement) {
7 ✔
6086
            return \str_replace($search, $replacement, $value);
7 ✔
6087
        };
7 ✔
6088

6089
        /* @phpstan-ignore-next-line return.type */
6090
        return $this->each($callable);
7 ✔
6091
    }
6092

6093
    /**
6094
     * Get the last elements from index $from until the end of this array.
6095
     *
6096
     * EXAMPLE: <code>
6097
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->rest(2); // Arrayy[0 => 'lall']
6098
     * </code>
6099
     *
6100
     * @param int $from
6101
     *
6102
     * @return static
6103
     *                <p>(Immutable)</p>
6104
     *
6105
     * @phpstan-return static
6106
     * @psalm-mutation-free
6107
     */
6108
    public function rest(int $from = 1): self
6109
    {
6110
        $tmpArray = $this->toArray();
105 ✔
6111

6112
        return static::create(
105 ✔
6113
            /* @phpstan-ignore-next-line argument.type */
6114
            \array_splice($tmpArray, $from),
105 ✔
6115
            $this->iteratorClass,
105 ✔
6116
            false
105 ✔
6117
        );
105 ✔
6118
    }
6119

6120
    /**
6121
     * Return the array in the reverse order.
6122
     *
6123
     * EXAMPLE: <code>
6124
     * a([1 => 1, 2 => 2, 3 => 3])->reverse(); // self[3, 2, 1]
6125
     * </code>
6126
     *
6127
     * @return $this
6128
     *               <p>(Mutable) Return this Arrayy object.</p>
6129
     *
6130
     * @phpstan-return static
6131
     */
6132
    public function reverse(): self
6133
    {
6134
        $this->generatorToArray();
63 ✔
6135

6136
        $this->array = \array_reverse($this->array);
63 ✔
6137

6138
        return $this;
63 ✔
6139
    }
6140

6141
    /**
6142
     * Return the array with keys in the reverse order.
6143
     *
6144
     * EXAMPLE: <code>
6145
     * a([1 => 1, 2 => 2, 3 => 3])->reverse(); // self[3 => 3, 2 => 2, 1 => 1]
6146
     * </code>
6147
     *
6148
     * @return $this
6149
     *               <p>(Mutable) Return this Arrayy object.</p>
6150
     *
6151
     * @phpstan-return static
6152
     */
6153
    public function reverseKeepIndex(): self
6154
    {
6155
        $this->generatorToArray();
35 ✔
6156

6157
        $this->array = \array_reverse($this->array, true);
35 ✔
6158

6159
        return $this;
35 ✔
6160
    }
6161

6162
    /**
6163
     * Sort an array in reverse order.
6164
     *
6165
     * @param int $sort_flags [optional] <p>
6166
     *                        You may modify the behavior of the sort using the optional
6167
     *                        parameter sort_flags, for details
6168
     *                        see sort.
6169
     *                        </p>
6170
     *
6171
     * @return $this
6172
     *               <p>(Mutable) Return this Arrayy object.</p>
6173
     *
6174
     * @phpstan-return static
6175
     */
6176
    public function rsort(int $sort_flags = 0): self
6177
    {
6178
        $this->generatorToArray();
28 ✔
6179

6180
        \rsort($this->array, $sort_flags);
28 ✔
6181

6182
        return $this;
28 ✔
6183
    }
6184

6185
    /**
6186
     * Sort an array in reverse order.
6187
     *
6188
     * @param int $sort_flags [optional] <p>
6189
     *                        You may modify the behavior of the sort using the optional
6190
     *                        parameter sort_flags, for details
6191
     *                        see sort.
6192
     *                        </p>
6193
     *
6194
     * @return $this
6195
     *               <p>(Immutable) Return this Arrayy object.</p>
6196
     *
6197
     * @phpstan-return static
6198
     * @psalm-mutation-free
6199
     */
6200
    public function rsortImmutable(int $sort_flags = 0): self
6201
    {
6202
        $that = clone $this;
28 ✔
6203

6204
        /**
6205
         * @psalm-suppress ImpureMethodCall - object is already cloned
6206
         */
6207
        $that->rsort($sort_flags);
28 ✔
6208

6209
        return $that;
28 ✔
6210
    }
6211

6212
    /**
6213
     * Search for the first index of the current array via $value.
6214
     *
6215
     * EXAMPLE: <code>
6216
     * a(['fΓ²Γ΄' => 'bΓ Ε™', 'lall' => 'bΓ Ε™'])->searchIndex('bΓ Ε™'); // Arrayy[0 => 'fΓ²Γ΄']
6217
     * </code>
6218
     *
6219
     * @param mixed $value
6220
     *
6221
     * @return false|int|string
6222
     *                          <p>Will return <b>FALSE</b> if the value can't be found.</p>
6223
     *
6224
     * @phpstan-param T $value
6225
     * @phpstan-return false|TKey
6226
     *
6227
     * @psalm-mutation-free
6228
     */
6229
    public function searchIndex($value)
6230
    {
6231
        foreach ($this->getGenerator() as $keyFromArray => $valueFromArray) {
147 ✔
6232
            if ($value === $valueFromArray) {
140 ✔
6233
                return $keyFromArray;
70 ✔
6234
            }
6235
        }
6236

6237
        return false;
77 ✔
6238
    }
6239

6240
    /**
6241
     * Search for the value of the current array via $index.
6242
     *
6243
     * EXAMPLE: <code>
6244
     * a(['fΓ²Γ΄' => 'bΓ Ε™'])->searchValue('fΓ²Γ΄'); // Arrayy[0 => 'bΓ Ε™']
6245
     * </code>
6246
     *
6247
     * @param mixed $index
6248
     *
6249
     * @return static
6250
     *                <p>(Immutable) Will return a empty Arrayy if the value wasn't found.</p>
6251
     *
6252
     * @phpstan-param TKey $index
6253
     * @phpstan-return static
6254
     * @psalm-mutation-free
6255
     */
6256
    public function searchValue($index): self
6257
    {
6258
        $this->generatorToArray();
63 ✔
6259

6260
        // init
6261
        $return = [];
63 ✔
6262

6263
        if ($this->array === []) {
63 ✔
6264
            return static::create(
×
6265
                /* @phpstan-ignore-next-line argument.type */
6266
                [],
×
6267
                $this->iteratorClass,
×
6268
                false
×
6269
            );
×
6270
        }
6271

6272
        // php cast "bool"-index into "int"-index
6273
        /* @phpstan-ignore identical.alwaysFalse */
6274
        if ((bool) $index === $index) {
63 ✔
6275
            $index = (int) $index;
7 ✔
6276
        }
6277

6278
        if ($this->offsetExists($index)) {
63 ✔
6279
            $return = [$this->array[$index]];
49 ✔
6280
        }
6281

6282
        return static::create(
63 ✔
6283
            /* @phpstan-ignore-next-line argument.type */
6284
            $return,
63 ✔
6285
            $this->iteratorClass,
63 ✔
6286
            false
63 ✔
6287
        );
63 ✔
6288
    }
6289

6290
    /**
6291
     * Set a value for the current array (optional using dot-notation).
6292
     *
6293
     * EXAMPLE: <code>
6294
     * $arrayy = a(['Lars' => ['lastname' => 'Moelleken']]);
6295
     * $arrayy->set('Lars.lastname', 'MΓΌller'); // Arrayy['Lars', ['lastname' => 'MΓΌller']]]
6296
     * </code>
6297
     *
6298
     * @param string $key   <p>The key to set.</p>
6299
     * @param mixed  $value <p>Its value.</p>
6300
     *
6301
     * @return $this
6302
     *               <p>(Mutable) Return this Arrayy object.</p>
6303
     *
6304
     * @phpstan-param  TKey $key
6305
     * @phpstan-param  T $value
6306
     * @phpstan-return static
6307
     */
6308
    public function set($key, $value): self
6309
    {
6310
        $this->internalSet($key, $value);
203 ✔
6311

6312
        return $this;
196 ✔
6313
    }
6314

6315
    /**
6316
     * Get a value from a array and set it if it was not.
6317
     *
6318
     * WARNING: this method only set the value, if the $key is not already set
6319
     *
6320
     * EXAMPLE: <code>
6321
     * $arrayy = a([1 => 1, 2 => 2, 3 => 3]);
6322
     * $arrayy->setAndGet(1, 4); // 1
6323
     * $arrayy->setAndGet(0, 4); // 4
6324
     * </code>
6325
     *
6326
     * @param mixed $key      <p>The key</p>
6327
     * @param mixed $fallback <p>The default value to set if it isn't.</p>
6328
     *
6329
     * @return mixed
6330
     *               <p>(Mutable)</p>
6331
     *
6332
     * @phpstan-param TKey $key
6333
     * @phpstan-param T $fallback
6334
     */
6335
    public function setAndGet($key, $fallback = null)
6336
    {
6337
        $this->generatorToArray();
77 ✔
6338

6339
        // If the key doesn't exist, set it.
6340
        if (!$this->has($key)) {
77 ✔
6341
            $this->array = $this->set($key, $fallback)->toArray();
28 ✔
6342
        }
6343

6344
        return $this->get($key);
77 ✔
6345
    }
6346

6347
    /**
6348
     * Shifts a specified value off the beginning of array.
6349
     *
6350
     * @return mixed|null
6351
     *                    <p>(Mutable) A shifted element from the current array.</p>
6352
     *
6353
     * @phpstan-return T|null
6354
     */
6355
    public function shift()
6356
    {
6357
        $this->generatorToArray();
35 ✔
6358

6359
        return \array_shift($this->array);
35 ✔
6360
    }
6361

6362
    /**
6363
     * Shuffle the current array.
6364
     *
6365
     * EXAMPLE: <code>
6366
     * a([1 => 'bar', 'foo' => 'foo'])->shuffle(); // e.g.: Arrayy[['foo' => 'foo', 1 => 'bar']]
6367
     * </code>
6368
     *
6369
     * @param bool       $secure <p>using a CSPRNG | @see https://paragonie.com/b/JvICXzh_jhLyt4y3</p>
6370
     * @param array|null $array  [optional]
6371
     *
6372
     * @return static
6373
     *                <p>(Immutable)</p>
6374
     *
6375
     * @phpstan-param  array<TKey,T> $array
6376
     * @phpstan-return static
6377
     */
6378
    public function shuffle(bool $secure = false, ?array $array = null): self
6379
    {
6380
        if ($array === null) {
14 ✔
6381
            $array = $this->toArray(false);
14 ✔
6382
        }
6383

6384
        if ($secure !== true) {
14 ✔
6385
            \shuffle($array);
14 ✔
6386
        } else {
6387
            $size = \count($array, \COUNT_NORMAL);
7 ✔
6388
            $keys = \array_keys($array);
7 ✔
6389
            for ($i = $size - 1; $i > 0; --$i) {
7 ✔
6390
                try {
6391
                    $r = \random_int(0, $i);
7 ✔
6392
                } catch (\Exception $e) {
×
6393
                    /** @noinspection RandomApiMigrationInspection - "random_int" is already in use */
6394
                    $r = \mt_rand(0, $i);
×
6395
                }
6396
                if ($r !== $i) {
7 ✔
6397
                    $temp = $array[$keys[$r]];
4 ✔
6398
                    $array[$keys[$r]] = $array[$keys[$i]];
4 ✔
6399
                    $array[$keys[$i]] = $temp;
4 ✔
6400
                }
6401
            }
6402
        }
6403

6404
        foreach ($array as $key => $value) {
14 ✔
6405
            // check if recursive is needed
6406
            if (\is_array($value)) {
14 ✔
6407
                /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
6408
                /** @phpstan-var array<TKey,T> $value */
6409
                $value = $value;
×
6410

6411
                $array[$key] = $this->shuffle($secure, $value);
×
6412
            }
6413
        }
6414

6415
        return static::create(
14 ✔
6416
            /* @phpstan-ignore-next-line argument.type */
6417
            $array,
14 ✔
6418
            $this->iteratorClass,
14 ✔
6419
            false
14 ✔
6420
        );
14 ✔
6421
    }
6422

6423
    /**
6424
     * Count the values from the current array.
6425
     *
6426
     * alias: for "Arrayy->count()"
6427
     *
6428
     * @param int $mode
6429
     *
6430
     * @return int
6431
     */
6432
    public function size(int $mode = \COUNT_NORMAL): int
6433
    {
6434
        return $this->count($mode);
140 ✔
6435
    }
6436

6437
    /**
6438
     * Checks whether array has exactly $size items.
6439
     *
6440
     * @param int $size
6441
     *
6442
     * @return bool
6443
     */
6444
    public function sizeIs(int $size): bool
6445
    {
6446
        // init
6447
        $itemsTempCount = 0;
7 ✔
6448

6449
        /** @noinspection PhpUnusedLocalVariableInspection */
6450
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
6451
        foreach ($this->getGeneratorByReference() as &$value) {
7 ✔
6452
            ++$itemsTempCount;
7 ✔
6453
            if ($itemsTempCount > $size) {
7 ✔
6454
                return false;
7 ✔
6455
            }
6456
        }
6457

6458
        return $itemsTempCount === $size;
7 ✔
6459
    }
6460

6461
    /**
6462
     * Checks whether array has between $fromSize to $toSize items. $toSize can be
6463
     * smaller than $fromSize.
6464
     *
6465
     * @param int $fromSize
6466
     * @param int $toSize
6467
     *
6468
     * @return bool
6469
     */
6470
    public function sizeIsBetween(int $fromSize, int $toSize): bool
6471
    {
6472
        if ($fromSize > $toSize) {
7 ✔
6473
            $tmp = $toSize;
7 ✔
6474
            $toSize = $fromSize;
7 ✔
6475
            $fromSize = $tmp;
7 ✔
6476
        }
6477

6478
        // init
6479
        $itemsTempCount = 0;
7 ✔
6480

6481
        /** @noinspection PhpUnusedLocalVariableInspection */
6482
        foreach ($this->getGenerator() as $value) {
7 ✔
6483
            ++$itemsTempCount;
7 ✔
6484
            if ($itemsTempCount > $toSize) {
7 ✔
6485
                return false;
7 ✔
6486
            }
6487
        }
6488

6489
        return $fromSize < $itemsTempCount && $itemsTempCount < $toSize;
7 ✔
6490
    }
6491

6492
    /**
6493
     * Checks whether array has more than $size items.
6494
     *
6495
     * @param int $size
6496
     *
6497
     * @return bool
6498
     */
6499
    public function sizeIsGreaterThan(int $size): bool
6500
    {
6501
        // init
6502
        $itemsTempCount = 0;
7 ✔
6503

6504
        /** @noinspection PhpUnusedLocalVariableInspection */
6505
        foreach ($this->getGenerator() as $value) {
7 ✔
6506
            ++$itemsTempCount;
7 ✔
6507
            if ($itemsTempCount > $size) {
7 ✔
6508
                return true;
7 ✔
6509
            }
6510
        }
6511

6512
        return $itemsTempCount > $size;
7 ✔
6513
    }
6514

6515
    /**
6516
     * Checks whether array has less than $size items.
6517
     *
6518
     * @param int $size
6519
     *
6520
     * @return bool
6521
     */
6522
    public function sizeIsLessThan(int $size): bool
6523
    {
6524
        // init
6525
        $itemsTempCount = 0;
7 ✔
6526

6527
        /** @noinspection PhpUnusedLocalVariableInspection */
6528
        foreach ($this->getGenerator() as $value) {
7 ✔
6529
            ++$itemsTempCount;
7 ✔
6530
            if ($itemsTempCount > $size) {
7 ✔
6531
                return false;
7 ✔
6532
            }
6533
        }
6534

6535
        return $itemsTempCount < $size;
7 ✔
6536
    }
6537

6538
    /**
6539
     * Counts all elements in an array, or something in an object.
6540
     *
6541
     * <p>
6542
     * For objects, if you have SPL installed, you can hook into count() by implementing interface {@see Countable}.
6543
     * The interface has exactly one method, {@see Countable::count()}, which returns the return value for the count()
6544
     * function. Please see the {@see Array} section of the manual for a detailed explanation of how arrays are
6545
     * implemented and used in PHP.
6546
     * </p>
6547
     *
6548
     * @return int
6549
     *             <p>
6550
     *             The number of elements in var, which is
6551
     *             typically an array, since anything else will have one
6552
     *             element.
6553
     *             </p>
6554
     *             <p>
6555
     *             If var is not an array or an object with
6556
     *             implemented Countable interface,
6557
     *             1 will be returned.
6558
     *             There is one exception, if var is &null;,
6559
     *             0 will be returned.
6560
     *             </p>
6561
     *             <p>
6562
     *             Caution: count may return 0 for a variable that isn't set,
6563
     *             but it may also return 0 for a variable that has been initialized with an
6564
     *             empty array. Use isset to test if a variable is set.
6565
     *             </p>
6566
     */
6567
    public function sizeRecursive(): int
6568
    {
6569
        return \count($this->toArray(), \COUNT_RECURSIVE);
70 ✔
6570
    }
6571

6572
    /**
6573
     * Extract a slice of the array.
6574
     *
6575
     * @param int      $offset       <p>Slice begin index.</p>
6576
     * @param int|null $length       <p>Length of the slice.</p>
6577
     * @param bool     $preserveKeys <p>Whether array keys are preserved or no.</p>
6578
     *
6579
     * @return static
6580
     *                <p>(Immutable) A slice of the original array with length $length.</p>
6581
     *
6582
     * @phpstan-return static
6583
     * @psalm-mutation-free
6584
     */
6585
    public function slice(int $offset, ?int $length = null, bool $preserveKeys = false)
6586
    {
6587
        return static::create(
35 ✔
6588
            /* @phpstan-ignore-next-line argument.type */
6589
            \array_slice(
35 ✔
6590
                $this->toArray(),
35 ✔
6591
                $offset,
35 ✔
6592
                $length,
35 ✔
6593
                $preserveKeys
35 ✔
6594
            ),
35 ✔
6595
            $this->iteratorClass,
35 ✔
6596
            false
35 ✔
6597
        );
35 ✔
6598
    }
6599

6600
    /**
6601
     * Sort the current array and optional you can keep the keys.
6602
     *
6603
     * EXAMPLE: <code>
6604
     * a(3 => 'd', 2 => 'f', 0 => 'a')->sort(SORT_ASC, SORT_NATURAL, false); // Arrayy[0 => 'a', 1 => 'd', 2 => 'f']
6605
     * </code>
6606
     *
6607
     * @param int|string $direction
6608
     *                              <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6609
     * @param int        $strategy
6610
     *                              <p>sort_flags => use e.g.: <strong>SORT_REGULAR</strong> (default) or
6611
     *                              <strong>SORT_NATURAL</strong></p>
6612
     * @param bool       $keepKeys
6613
     *
6614
     * @return static
6615
     *                <p>(Mutable) Return this Arrayy object.</p>
6616
     *
6617
     * @phpstan-return static
6618
     */
6619
    public function sort(
6620
        $direction = \SORT_ASC,
6621
        int $strategy = \SORT_REGULAR,
6622
        bool $keepKeys = false
6623
    ): self {
6624
        $this->generatorToArray();
140 ✔
6625

6626
        return $this->sorting(
140 ✔
6627
            $this->array,
140 ✔
6628
            $direction,
140 ✔
6629
            $strategy,
140 ✔
6630
            $keepKeys
140 ✔
6631
        );
140 ✔
6632
    }
6633

6634
    /**
6635
     * Sort the current array and optional you can keep the keys.
6636
     *
6637
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6638
     * @param int        $strategy  <p>sort_flags => use e.g.: <strong>SORT_REGULAR</strong> (default) or
6639
     *                              <strong>SORT_NATURAL</strong></p>
6640
     * @param bool       $keepKeys
6641
     *
6642
     * @return static
6643
     *                <p>(Immutable) Return this Arrayy object.</p>
6644
     *
6645
     * @phpstan-return static
6646
     */
6647
    public function sortImmutable(
6648
        $direction = \SORT_ASC,
6649
        int $strategy = \SORT_REGULAR,
6650
        bool $keepKeys = false
6651
    ): self {
6652
        $that = clone $this;
84 ✔
6653

6654
        $that->generatorToArray();
84 ✔
6655

6656
        return $that->sorting(
84 ✔
6657
            $that->array,
84 ✔
6658
            $direction,
84 ✔
6659
            $strategy,
84 ✔
6660
            $keepKeys
84 ✔
6661
        );
84 ✔
6662
    }
6663

6664
    /**
6665
     * Sort the current array by key.
6666
     *
6667
     * EXAMPLE: <code>
6668
     * a([1 => 2, 0 => 1])->sortKeys(\SORT_ASC); // Arrayy[0 => 1, 1 => 2]
6669
     * </code>
6670
     *
6671
     * @see http://php.net/manual/en/function.ksort.php
6672
     * @see http://php.net/manual/en/function.krsort.php
6673
     *
6674
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6675
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6676
     *                              <strong>SORT_NATURAL</strong></p>
6677
     *
6678
     * @return $this
6679
     *               <p>(Mutable) Return this Arrayy object.</p>
6680
     *
6681
     * @phpstan-return static
6682
     */
6683
    public function sortKeys(
6684
        $direction = \SORT_ASC,
6685
        int $strategy = \SORT_REGULAR
6686
    ): self {
6687
        $this->generatorToArray();
126 ✔
6688

6689
        $this->sorterKeys($this->array, $direction, $strategy);
126 ✔
6690

6691
        return $this;
126 ✔
6692
    }
6693

6694
    /**
6695
     * Sort the current array by key.
6696
     *
6697
     * @see          http://php.net/manual/en/function.ksort.php
6698
     * @see          http://php.net/manual/en/function.krsort.php
6699
     *
6700
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6701
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6702
     *                              <strong>SORT_NATURAL</strong></p>
6703
     *
6704
     * @return $this
6705
     *               <p>(Immutable) Return this Arrayy object.</p>
6706
     *
6707
     * @phpstan-return static
6708
     * @psalm-mutation-free
6709
     */
6710
    public function sortKeysImmutable(
6711
        $direction = \SORT_ASC,
6712
        int $strategy = \SORT_REGULAR
6713
    ): self {
6714
        $that = clone $this;
56 ✔
6715

6716
        /**
6717
         * @psalm-suppress ImpureMethodCall - object is already cloned
6718
         */
6719
        $that->sortKeys($direction, $strategy);
56 ✔
6720

6721
        return $that;
56 ✔
6722
    }
6723

6724
    /**
6725
     * Sort the current array by value.
6726
     *
6727
     * EXAMPLE: <code>
6728
     * a(3 => 'd', 2 => 'f', 0 => 'a')->sortValueKeepIndex(SORT_ASC, SORT_REGULAR); // Arrayy[0 => 'a', 3 => 'd', 2 => 'f']
6729
     * </code>
6730
     *
6731
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6732
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6733
     *                              <strong>SORT_NATURAL</strong></p>
6734
     *
6735
     * @return static
6736
     *                <p>(Mutable)</p>
6737
     *
6738
     * @phpstan-return static
6739
     */
6740
    public function sortValueKeepIndex(
6741
        $direction = \SORT_ASC,
6742
        int $strategy = \SORT_REGULAR
6743
    ): self {
6744
        return $this->sort($direction, $strategy, true);
7 ✔
6745
    }
6746

6747
    /**
6748
     * Sort the current array by value.
6749
     *
6750
     * EXAMPLE: <code>
6751
     * a(3 => 'd', 2 => 'f', 0 => 'a')->sortValueNewIndex(SORT_ASC, SORT_NATURAL); // Arrayy[0 => 'a', 1 => 'd', 2 => 'f']
6752
     * </code>
6753
     *
6754
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6755
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6756
     *                              <strong>SORT_NATURAL</strong></p>
6757
     *
6758
     * @return static
6759
     *                <p>(Mutable)</p>
6760
     *
6761
     * @phpstan-return static
6762
     */
6763
    public function sortValueNewIndex($direction = \SORT_ASC, int $strategy = \SORT_REGULAR): self
6764
    {
6765
        return $this->sort($direction, $strategy, false);
7 ✔
6766
    }
6767

6768
    /**
6769
     * Sort a array by value or by a closure.
6770
     *
6771
     * - If the sorter is null, the array is sorted naturally.
6772
     * - Associative (string) keys will be maintained, but numeric keys will be re-indexed.
6773
     *
6774
     * EXAMPLE: <code>
6775
     * $testArray = range(1, 5);
6776
     * $under = a($testArray)->sorter(
6777
     *     function ($value) {
6778
     *         return $value % 2 === 0;
6779
     *     }
6780
     * );
6781
     * var_dump($under); // Arrayy[1, 3, 5, 2, 4]
6782
     * </code>
6783
     *
6784
     * @param callable|mixed|null $sorter
6785
     * @param int|string          $direction <p>use <strong>SORT_ASC</strong> (default) or
6786
     *                                       <strong>SORT_DESC</strong></p>
6787
     * @param int                 $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6788
     *                                       <strong>SORT_NATURAL</strong></p>
6789
     *
6790
     * @return static
6791
     *                <p>(Immutable)</p>
6792
     *
6793
     * @pslam-param callable|T|null $sorter
6794
     * @phpstan-return static
6795
     * @psalm-mutation-free
6796
     */
6797
    public function sorter($sorter = null, $direction = \SORT_ASC, int $strategy = \SORT_REGULAR): self
6798
    {
6799
        $array = $this->toArray();
7 ✔
6800
        $direction = $this->getDirection($direction);
7 ✔
6801

6802
        // Transform all values into their results.
6803
        if ($sorter) {
7 ✔
6804
            $arrayy = static::create(
7 ✔
6805
                /* @phpstan-ignore-next-line argument.type */
6806
                $array,
7 ✔
6807
                $this->iteratorClass,
7 ✔
6808
                false
7 ✔
6809
            );
7 ✔
6810

6811
            /**
6812
             * @psalm-suppress MissingClosureReturnType
6813
             * @psalm-suppress MissingClosureParamType
6814
             */
6815
            $results = $arrayy->each(
7 ✔
6816
                static function ($value) use ($sorter) {
7 ✔
6817
                    if (\is_callable($sorter) === true) {
7 ✔
6818
                        return $sorter($value);
7 ✔
6819
                    }
6820

6821
                    return $sorter === $value;
7 ✔
6822
                }
7 ✔
6823
            );
7 ✔
6824

6825
            $results = $results->toArray();
7 ✔
6826
        } else {
6827
            $results = $array;
7 ✔
6828
        }
6829

6830
        // Sort by the results and replace by original values
6831
        \array_multisort($results, $direction, $strategy, $array);
7 ✔
6832

6833
        return static::create(
7 ✔
6834
            /* @phpstan-ignore-next-line argument.type */
6835
            $array,
7 ✔
6836
            $this->iteratorClass,
7 ✔
6837
            false
7 ✔
6838
        );
7 ✔
6839
    }
6840

6841
    /**
6842
     * @param int      $offset
6843
     * @param int|null $length
6844
     * @param array    $replacement
6845
     *
6846
     * @return static
6847
     *                <p>(Immutable)</p>
6848
     *
6849
     * @phpstan-param  array<T> $replacement
6850
     * @phpstan-return static
6851
     * @psalm-mutation-free
6852
     */
6853
    public function splice(int $offset, ?int $length = null, $replacement = []): self
6854
    {
6855
        $tmpArray = $this->toArray();
7 ✔
6856

6857
        \array_splice(
7 ✔
6858
            $tmpArray,
7 ✔
6859
            $offset,
7 ✔
6860
            $length ?? $this->count(),
7 ✔
6861
            $replacement
7 ✔
6862
        );
7 ✔
6863

6864
        return static::create(
7 ✔
6865
            /* @phpstan-ignore-next-line argument.type */
6866
            $tmpArray,
7 ✔
6867
            $this->iteratorClass,
7 ✔
6868
            false
7 ✔
6869
        );
7 ✔
6870
    }
6871

6872
    /**
6873
     * Split an array in the given amount of pieces.
6874
     *
6875
     * EXAMPLE: <code>
6876
     * a(['a' => 1, 'b' => 2])->split(2, true); // Arrayy[['a' => 1], ['b' => 2]]
6877
     * </code>
6878
     *
6879
     * @param int  $numberOfPieces
6880
     * @param bool $keepKeys
6881
     *
6882
     * @return static
6883
     *                <p>(Immutable)</p>
6884
     *
6885
     * @phpstan-return static
6886
     * @psalm-mutation-free
6887
     */
6888
    public function split(int $numberOfPieces = 2, bool $keepKeys = false): self
6889
    {
6890
        if ($keepKeys) {
7 ✔
6891
            $generator = function () use ($numberOfPieces) {
7 ✔
6892
                $carry = [];
7 ✔
6893
                $i = 1;
7 ✔
6894
                foreach ($this->getGenerator() as $key => $value) {
7 ✔
6895
                    $carry[$key] = $value;
7 ✔
6896

6897
                    if ($i % $numberOfPieces !== 0) {
7 ✔
6898
                        ++$i;
7 ✔
6899

6900
                        continue;
7 ✔
6901
                    }
6902

6903
                    yield $carry;
7 ✔
6904

6905
                    $carry = [];
7 ✔
6906
                    $i = 1;
7 ✔
6907
                }
6908

6909
                if ($carry !== []) {
7 ✔
6910
                    yield $carry;
7 ✔
6911
                }
6912
            };
7 ✔
6913
        } else {
6914
            $generator = function () use ($numberOfPieces) {
7 ✔
6915
                $carry = [];
7 ✔
6916
                $i = 1;
7 ✔
6917
                foreach ($this->getGenerator() as $value) {
7 ✔
6918
                    $carry[] = $value;
7 ✔
6919

6920
                    if ($i % $numberOfPieces !== 0) {
7 ✔
6921
                        ++$i;
7 ✔
6922

6923
                        continue;
7 ✔
6924
                    }
6925

6926
                    yield $carry;
7 ✔
6927

6928
                    $carry = [];
7 ✔
6929
                    $i = 1;
7 ✔
6930
                }
6931

6932
                if ($carry !== []) {
7 ✔
6933
                    yield $carry;
7 ✔
6934
                }
6935
            };
7 ✔
6936
        }
6937

6938
        return static::create(
7 ✔
6939
            $generator,
7 ✔
6940
            $this->iteratorClass,
7 ✔
6941
            false
7 ✔
6942
        );
7 ✔
6943
    }
6944

6945
    /**
6946
     * Strip all empty items from the current array.
6947
     *
6948
     * EXAMPLE: <code>
6949
     * a(['a' => 1, 'b' => ''])->stripEmpty(); // Arrayy[['a' => 1]]
6950
     * </code>
6951
     *
6952
     * @return static
6953
     *                <p>(Immutable)</p>
6954
     *
6955
     * @phpstan-return static
6956
     * @psalm-mutation-free
6957
     */
6958
    public function stripEmpty(): self
6959
    {
6960
        $generator = function () {
7 ✔
6961
            foreach ($this->getGenerator() as $key => $item) {
7 ✔
6962
                if ($item === null) {
7 ✔
6963
                    continue;
7 ✔
6964
                }
6965

6966
                if ((bool) \trim((string) $item)) {
7 ✔
6967
                    yield $key => $item;
7 ✔
6968
                }
6969
            }
6970
        };
7 ✔
6971

6972
        return static::create(
7 ✔
6973
            $generator(),
7 ✔
6974
            $this->iteratorClass,
7 ✔
6975
            false
7 ✔
6976
        );
7 ✔
6977
    }
6978

6979
    /**
6980
     * Swap two values between positions by key.
6981
     *
6982
     * EXAMPLE: <code>
6983
     * a(['a' => 1, 'b' => ''])->swap('a', 'b'); // Arrayy[['a' => '', 'b' => 1]]
6984
     * </code>
6985
     *
6986
     * @param int|string $swapA <p>a key in the array</p>
6987
     * @param int|string $swapB <p>a key in the array</p>
6988
     *
6989
     * @return static
6990
     *                <p>(Immutable)</p>
6991
     *
6992
     * @phpstan-return static
6993
     * @psalm-mutation-free
6994
     */
6995
    public function swap($swapA, $swapB): self
6996
    {
6997
        $array = $this->toArray();
7 ✔
6998

6999
        list($array[$swapA], $array[$swapB]) = [$array[$swapB], $array[$swapA]];
7 ✔
7000

7001
        return static::create(
7 ✔
7002
            /* @phpstan-ignore-next-line argument.type */
7003
            $array,
7 ✔
7004
            $this->iteratorClass,
7 ✔
7005
            false
7 ✔
7006
        );
7 ✔
7007
    }
7008

7009
    /**
7010
     * Get the current array from the "Arrayy"-object.
7011
     * alias for "getArray()"
7012
     *
7013
     * @param bool $convertAllArrayyElements <p>
7014
     *                                       Convert all Child-"Arrayy" objects also to arrays.
7015
     *                                       </p>
7016
     * @param bool $preserveKeys             <p>
7017
     *                                       e.g.: A generator maybe return the same key more than once,
7018
     *                                       so maybe you will ignore the keys.
7019
     *                                       </p>
7020
     *
7021
     * @return array
7022
     *
7023
     * @phpstan-return ($preserveKeys is true ? array<TKey,T> : T[])
7024
     * @psalm-mutation-free
7025
     */
7026
    public function toArray(
7027
        bool $convertAllArrayyElements = false,
7028
        bool $preserveKeys = true
7029
    ): array {
7030
        if ($convertAllArrayyElements) {
6,776 ✔
7031
            // init
7032
            $array = [];
21 ✔
7033

7034
            foreach ($this->getGenerator() as $key => $value) {
21 ✔
7035
                if ($value instanceof self) {
21 ✔
7036
                    $value = $value->toArray(
14 ✔
7037
                        $convertAllArrayyElements,
14 ✔
7038
                        $preserveKeys
14 ✔
7039
                    );
14 ✔
7040
                }
7041

7042
                if ($preserveKeys) {
21 ✔
7043
                    $array[$key] = $value;
14 ✔
7044
                } else {
7045
                    $array[] = $value;
7 ✔
7046
                }
7047
            }
7048

7049
            /* @phpstan-ignore return.type */
7050
            return $array;
21 ✔
7051
        }
7052

7053
        return \iterator_to_array($this->getGenerator(), $preserveKeys);
6,769 ✔
7054
    }
7055

7056
    /**
7057
     * Get the current array from the "Arrayy"-object as list.
7058
     *
7059
     * @param bool $convertAllArrayyElements <p>
7060
     *                                       Convert all Child-"Arrayy" objects also to arrays.
7061
     *                                       </p>
7062
     *
7063
     * @return array
7064
     *
7065
     * @phpstan-return list<T>
7066
     * @psalm-mutation-free
7067
     */
7068
    public function toList(bool $convertAllArrayyElements = false): array
7069
    {
7070
        /** @var list<T> - currently phpstan can't return different types depending on the phpdocs params */
7071
        return $this->toArray(
7 ✔
7072
            $convertAllArrayyElements,
7 ✔
7073
            false
7 ✔
7074
        );
7 ✔
7075
    }
7076

7077
    /**
7078
     * Convert the current array to JSON.
7079
     *
7080
     * EXAMPLE: <code>
7081
     * a(['bar', ['foo']])->toJson(); // '["bar",{"1":"foo"}]'
7082
     * </code>
7083
     *
7084
     * @param int $options [optional] <p>e.g. JSON_PRETTY_PRINT</p>
7085
     * @param int $depth   [optional] <p>Set the maximum depth. Must be greater than zero.</p>
7086
     *
7087
     * @return string
7088
     */
7089
    public function toJson(int $options = 0, int $depth = 512): string
7090
    {
7091
        if ($depth < 1) {
91 ✔
7092
            $depth = 1;
×
7093
        }
7094

7095
        $return = \json_encode($this->toArray(), $options, $depth);
91 ✔
7096
        if ($return === false) {
91 ✔
7097
            return '';
×
7098
        }
7099

7100
        return $return;
91 ✔
7101
    }
7102

7103
    /**
7104
     * @param string[]|null $items  [optional]
7105
     * @param string[]      $helper [optional]
7106
     *
7107
     * @return static|static[]
7108
     *
7109
     * @phpstan-return static
7110
     */
7111
    public function toPermutation(?array $items = null, array $helper = []): self
7112
    {
7113
        // init
7114
        $return = [];
7 ✔
7115

7116
        if ($items === null) {
7 ✔
7117
            $items = $this->toArray();
7 ✔
7118
        }
7119

7120
        if (empty($items)) {
7 ✔
7121
            $return[] = $helper;
7 ✔
7122
        } else {
7123
            for ($i = \count($items) - 1; $i >= 0; --$i) {
7 ✔
7124
                $new_items = $items;
7 ✔
7125
                $new_helper = $helper;
7 ✔
7126
                list($tmp_helper) = \array_splice($new_items, $i, 1);
7 ✔
7127
                /** @noinspection PhpSillyAssignmentInspection */
7128
                /** @var string[] $new_items */
7129
                $new_items = $new_items;
7 ✔
7130
                \array_unshift($new_helper, $tmp_helper);
7 ✔
7131
                $return = \array_merge(
7 ✔
7132
                    $return,
7 ✔
7133
                    $this->toPermutation($new_items, $new_helper)->toArray()
7 ✔
7134
                );
7 ✔
7135
            }
7136
        }
7137

7138
        /** @var static $return  - help for phpstan */
7139
        $return = static::create(
7 ✔
7140
            /* @phpstan-ignore-next-line argument.type */
7141
            $return,
7 ✔
7142
            $this->iteratorClass,
7 ✔
7143
            false
7 ✔
7144
        );
7 ✔
7145

7146
        return $return;
7 ✔
7147
    }
7148

7149
    /**
7150
     * Implodes array to a string with specified separator.
7151
     *
7152
     * @param string $separator [optional] <p>The element's separator.</p>
7153
     *
7154
     * @return string
7155
     *                <p>The string representation of array, separated by ",".</p>
7156
     */
7157
    public function toString(string $separator = ','): string
7158
    {
7159
        return $this->implode($separator);
133 ✔
7160
    }
7161

7162
    /**
7163
     * Return a duplicate free copy of the current array.
7164
     *
7165
     * EXAMPLE: <code>
7166
     * a([2 => 1, 3 => 2, 4 => 2])->uniqueNewIndex(); // Arrayy[1, 2]
7167
     * </code>
7168
     *
7169
     * @return $this
7170
     *               <p>(Mutable)</p>
7171
     *
7172
     * @phpstan-return static
7173
     */
7174
    public function uniqueNewIndex(): self
7175
    {
7176
        // INFO: \array_unique() can't handle e.g. "stdClass"-values in an array
7177

7178
        $this->array = $this->reduce(
91 ✔
7179
            static function ($resultArray, $value, $key) {
91 ✔
7180
                if (!\in_array($value, $resultArray, true)) {
84 ✔
7181
                    $resultArray[] = $value;
84 ✔
7182
                }
7183

7184
                return $resultArray;
84 ✔
7185
            },
91 ✔
7186
            []
91 ✔
7187
        )->toArray();
91 ✔
7188
        $this->generator = null;
91 ✔
7189

7190
        return $this;
91 ✔
7191
    }
7192

7193
    /**
7194
     * Return a duplicate free copy of the current array. (with the old keys)
7195
     *
7196
     * EXAMPLE: <code>
7197
     * a([2 => 1, 3 => 2, 4 => 2])->uniqueNewIndex(); // Arrayy[2 => 1, 3 => 2]
7198
     * </code>
7199
     *
7200
     * @return $this
7201
     *               <p>(Mutable)</p>
7202
     *
7203
     * @phpstan-return static
7204
     */
7205
    public function uniqueKeepIndex(): self
7206
    {
7207
        // INFO: \array_unique() can't handle e.g. "stdClass"-values in an array
7208

7209
        // init
7210
        $array = $this->toArray();
77 ✔
7211

7212
        /**
7213
         * @psalm-suppress MissingClosureReturnType
7214
         * @psalm-suppress MissingClosureParamType
7215
         */
7216
        $this->array = \array_reduce(
77 ✔
7217
            \array_keys($array),
77 ✔
7218
            static function ($resultArray, $key) use ($array) {
77 ✔
7219
                if (!\in_array($array[$key], $resultArray, true)) {
70 ✔
7220
                    $resultArray[$key] = $array[$key];
70 ✔
7221
                }
7222

7223
                return $resultArray;
70 ✔
7224
            },
77 ✔
7225
            []
77 ✔
7226
        );
77 ✔
7227
        $this->generator = null;
77 ✔
7228

7229
        return $this;
77 ✔
7230
    }
7231

7232
    /**
7233
     * alias: for "Arrayy->uniqueNewIndex()"
7234
     *
7235
     * @return static
7236
     *                <p>(Mutable) Return this Arrayy object, with the appended values.</p>
7237
     *
7238
     * @see          Arrayy::unique()
7239
     *
7240
     * @phpstan-return static
7241
     */
7242
    public function unique(): self
7243
    {
7244
        return $this->uniqueNewIndex();
91 ✔
7245
    }
7246

7247
    /**
7248
     * Prepends one or more values to the beginning of array at once.
7249
     *
7250
     * @param mixed ...$args
7251
     *
7252
     * @return $this
7253
     *               <p>(Mutable) Return this Arrayy object, with prepended elements to the beginning of array.</p>
7254
     *
7255
     * @phpstan-param  array<TKey,T> ...$args
7256
     * @phpstan-return static
7257
     */
7258
    public function unshift(...$args): self
7259
    {
7260
        $this->generatorToArray();
42 ✔
7261

7262
        if (
7263
            $this->checkPropertyTypes
42 ✔
7264
            &&
7265
            $this->properties !== []
42 ✔
7266
        ) {
7267
            foreach ($args as $key => $value) {
14 ✔
7268
                $this->checkType($key, $value);
14 ✔
7269
            }
7270
        }
7271

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

7274
        return $this;
35 ✔
7275
    }
7276

7277
    /**
7278
     * Tests whether the given closure return something valid for all elements of this array.
7279
     *
7280
     * @param \Closure $closure the predicate
7281
     *
7282
     * @return bool
7283
     *              <p>TRUE, if the predicate yields TRUE for all elements, FALSE otherwise.</p>
7284
     *
7285
     * @phpstan-param \Closure(T,TKey):bool $closure
7286
     */
7287
    public function validate(\Closure $closure): bool
7288
    {
7289
        foreach ($this->getGenerator() as $key => $value) {
7 ✔
7290
            if (!$closure($value, $key)) {
7 ✔
7291
                return false;
7 ✔
7292
            }
7293
        }
7294

7295
        return true;
7 ✔
7296
    }
7297

7298
    /**
7299
     * Get all values from a array.
7300
     *
7301
     * EXAMPLE: <code>
7302
     * $arrayy = a([1 => 'foo', 2 => 'foo2', 3 => 'bar']);
7303
     * $arrayyTmp->values(); // Arrayy[0 => 'foo', 1 => 'foo2', 2 => 'bar']
7304
     * </code>
7305
     *
7306
     * @return static
7307
     *                <p>(Immutable)</p>
7308
     *
7309
     * @phpstan-return static
7310
     * @psalm-mutation-free
7311
     */
7312
    public function values(): self
7313
    {
7314
        return static::create(
14 ✔
7315
            function () {
14 ✔
7316
                foreach ($this->getGenerator() as $value) {
14 ✔
7317
                    yield $value;
14 ✔
7318
                }
7319
            },
14 ✔
7320
            $this->iteratorClass,
14 ✔
7321
            false
14 ✔
7322
        );
14 ✔
7323
    }
7324

7325
    /**
7326
     * Apply the given function to every element in the array, discarding the results.
7327
     *
7328
     * EXAMPLE: <code>
7329
     * $callable = function (&$value, $key) {
7330
     *     $value = $key;
7331
     * };
7332
     * $arrayy = a([1, 2, 3]);
7333
     * $arrayy->walk($callable); // Arrayy[0, 1, 2]
7334
     * </code>
7335
     *
7336
     * @param callable $callable
7337
     * @param bool     $recursive
7338
     *                            [optional] <p>Whether array will be walked recursively or no</p>
7339
     * @param mixed    $userData
7340
     *                            [optional] <p>
7341
     *                            If the optional $userData parameter is supplied,
7342
     *                            it will be passed as the third parameter to the $callable.
7343
     *                            </p>
7344
     *
7345
     * @return $this
7346
     *               <p>(Mutable) Return this Arrayy object, with modified elements.</p>
7347
     *
7348
     * @template TExtra
7349
     *              <p>The extra input value type.</p>
7350
     *
7351
     * @phostan-param TExtra $userData
7352
     * @phpstan-param  callable(T,TKey,?TExtra):void $callable
7353
     * @phpstan-return static
7354
     */
7355
    public function walk(
7356
        $callable,
7357
        bool $recursive = false,
7358
        $userData = self::ARRAYY_HELPER_WALK
7359
    ): self {
7360
        $this->generatorToArray();
84 ✔
7361

7362
        if ($this->array !== []) {
84 ✔
7363
            if ($recursive === true) {
70 ✔
7364
                if ($userData !== self::ARRAYY_HELPER_WALK) {
35 ✔
7365
                    \array_walk_recursive($this->array, $callable, $userData);
×
7366
                } else {
7367
                    \array_walk_recursive($this->array, $callable);
35 ✔
7368
                }
7369
            } else {
7370
                if ($userData !== self::ARRAYY_HELPER_WALK) {
35 ✔
7371
                    \array_walk($this->array, $callable, $userData);
×
7372
                } else {
7373
                    /* @phpstan-ignore argument.type */
7374
                    \array_walk($this->array, $callable);
35 ✔
7375
                }
7376
            }
7377
        }
7378

7379
        return $this;
84 ✔
7380
    }
7381

7382
    /**
7383
     * Returns a collection of matching items.
7384
     *
7385
     * @param string $keyOrPropertyOrMethod
7386
     *                                      <p>The property or method to evaluate.</p>
7387
     * @param mixed  $value
7388
     *                                      <p>The value to match.</p>
7389
     *
7390
     * @throws \InvalidArgumentException if property or method is not defined
7391
     *
7392
     * @return static
7393
     *
7394
     * @phpstan-return static
7395
     */
7396
    public function where(string $keyOrPropertyOrMethod, $value): self
7397
    {
7398
        return $this->filter(
14 ✔
7399
            function ($item) use ($keyOrPropertyOrMethod, $value) {
14 ✔
7400
                $accessorValue = $this->extractValue(
7 ✔
7401
                    /* @phpstan-ignore-next-line argument.type (filter() does not retain the item type in this callback) */
7402
                    $item,
7 ✔
7403
                    $keyOrPropertyOrMethod
7 ✔
7404
                );
7 ✔
7405

7406
                return $accessorValue === $value;
7 ✔
7407
            }
14 ✔
7408
        );
14 ✔
7409
    }
7410

7411
    /**
7412
     * Convert an array into an object.
7413
     *
7414
     * @param array $array
7415
     *
7416
     * @return \stdClass
7417
     *
7418
     * @phpstan-param array<int|string,mixed> $array
7419
     */
7420
    final protected static function arrayToObject(array $array = []): \stdClass
7421
    {
7422
        // init
7423
        $object = new \stdClass();
28 ✔
7424

7425
        if (\count($array, \COUNT_NORMAL) <= 0) {
28 ✔
7426
            return $object;
7 ✔
7427
        }
7428

7429
        foreach ($array as $name => $value) {
21 ✔
7430
            if (\is_array($value)) {
21 ✔
7431
                $object->{$name} = static::arrayToObject($value);
7 ✔
7432
            } else {
7433
                $object->{$name} = $value;
21 ✔
7434
            }
7435
        }
7436

7437
        return $object;
21 ✔
7438
    }
7439

7440
    /**
7441
     * @param array|\Generator|null $input         <p>
7442
     *                                             An array containing keys to return.
7443
     *                                             </p>
7444
     * @param mixed|null            $search_values [optional] <p>
7445
     *                                             If specified, then only keys containing these values are returned.
7446
     *                                             </p>
7447
     * @param bool                  $strict        [optional] <p>
7448
     *                                             Determines if strict comparison (===) should be used during the
7449
     *                                             search.
7450
     *                                             </p>
7451
     *
7452
     * @return array
7453
     *               <p>An array of all the keys in input.</p>
7454
     *
7455
     * @template TInput
7456
     *
7457
     * @phpstan-param  array<array-key,TInput>|\Generator<array-key,TInput>|null $input
7458
     * @phpstan-param T|T[]|null $search_values
7459
     * @phpstan-return array<int, TKey>
7460
     *
7461
     * @psalm-mutation-free
7462
     */
7463
    protected function array_keys_recursive(
7464
        $input = null,
7465
        $search_values = null,
7466
        bool $strict = true
7467
    ): array {
7468
        // init
7469
        $keys = [];
77 ✔
7470
        $keysTmp = [];
77 ✔
7471

7472
        if ($input === null) {
77 ✔
7473
            $input = $this->getGenerator();
28 ✔
7474
        }
7475

7476
        if ($search_values === null) {
77 ✔
7477
            foreach ($input as $key => $value) {
77 ✔
7478
                $keys[] = $key;
77 ✔
7479

7480
                // check if recursive is needed
7481
                if (\is_array($value)) {
77 ✔
7482
                    $keysTmp[] = $this->array_keys_recursive($value);
28 ✔
7483
                }
7484
            }
7485
        } else {
7486
            $is_array_tmp = \is_array($search_values);
7 ✔
7487

7488
            foreach ($input as $key => $value) {
7 ✔
7489
                if (
7490
                    (
7491
                        $is_array_tmp === false
7 ✔
7492
                        &&
7 ✔
7493
                        $strict === true
7 ✔
7494
                        &&
7 ✔
7495
                        $search_values === $value
7 ✔
7496
                    )
7497
                    ||
7498
                    (
7499
                        $is_array_tmp === false
7 ✔
7500
                        &&
7 ✔
7501
                        $strict === false
7 ✔
7502
                        &&
7 ✔
7503
                        $search_values == $value
7 ✔
7504
                    )
7505
                    ||
7506
                    (
7507
                        $is_array_tmp === true
7 ✔
7508
                        &&
7 ✔
7509
                        \in_array($value, $search_values, $strict)
7 ✔
7510
                    )
7511
                ) {
7512
                    $keys[] = $key;
7 ✔
7513
                }
7514

7515
                // check if recursive is needed
7516
                if (\is_array($value)) {
7 ✔
7517
                    $keysTmp[] = $this->array_keys_recursive($value);
7 ✔
7518
                }
7519
            }
7520
        }
7521

7522
        return $keysTmp === [] ? $keys : \array_merge($keys, ...$keysTmp);
77 ✔
7523
    }
7524

7525
    /**
7526
     * @param string     $path
7527
     * @param callable   $callable
7528
     * @param array|null $currentOffset
7529
     *
7530
     * @return void
7531
     *
7532
     * @phpstan-param array<array-key,mixed>|null $currentOffset
7533
     * @psalm-mutation-free
7534
     */
7535
    protected function callAtPath($path, $callable, &$currentOffset = null)
7536
    {
7537
        $this->generatorToArray();
77 ✔
7538

7539
        if ($currentOffset === null) {
77 ✔
7540
            $currentOffset = &$this->array;
77 ✔
7541
        }
7542

7543
        $explodedPath = \explode($this->pathSeparator, $path);
77 ✔
7544

7545
        $nextPath = \array_shift($explodedPath);
77 ✔
7546
        if (!isset($currentOffset[$nextPath])) {
77 ✔
7547
            return;
7 ✔
7548
        }
7549

7550
        if ($explodedPath !== []) {
70 ✔
7551
            if (!\is_array($currentOffset[$nextPath])) {
7 ✔
NEW
7552
                return;
×
7553
            }
7554

7555
            $nestedOffset = &$currentOffset[$nextPath];
7 ✔
7556
            $this->callAtPath(
7 ✔
7557
                \implode($this->pathSeparator, $explodedPath),
7 ✔
7558
                $callable,
7 ✔
7559
                $nestedOffset
7 ✔
7560
            );
7 ✔
7561
        } else {
7562
            $callable($currentOffset[$nextPath]);
70 ✔
7563
        }
7564
    }
7565

7566
    /**
7567
     * Extracts the value of the given property or method from the object.
7568
     *
7569
     * @param array|object $object
7570
     *                                         <p>The object to extract the value from.</p>
7571
     * @param string    $keyOrPropertyOrMethod
7572
     *                                         <p>The property or method for which the
7573
     *                                         value should be extracted.</p>
7574
     *
7575
     * @throws \InvalidArgumentException if the method or property is not defined
7576
     *
7577
     * @return mixed
7578
     *               <p>The value extracted from the specified property or method.</p>
7579
     *
7580
     * @phpstan-param array<array-key,mixed>|object $object
7581
     */
7582
    final protected function extractValue($object, string $keyOrPropertyOrMethod)
7583
    {
7584
        if (\is_array($object)) {
14 ✔
7585
            if (\array_key_exists($keyOrPropertyOrMethod, $object)) {
7 ✔
7586
                return $object[$keyOrPropertyOrMethod];
7 ✔
7587
            }
7588
        } elseif ($object instanceof self && isset($object[$keyOrPropertyOrMethod])) {
14 ✔
7589
            $return = $object->get($keyOrPropertyOrMethod);
7 ✔
7590

7591
            if ($return instanceof self) {
7 ✔
7592
                return $return->toArray();
×
7593
            }
7594

7595
            return $return;
7 ✔
7596
        }
7597

7598
        if (\is_object($object) && \property_exists($object, $keyOrPropertyOrMethod)) {
7 ✔
7599
            return $object->{$keyOrPropertyOrMethod};
7 ✔
7600
        }
7601

NEW
7602
        if (\is_object($object) && \method_exists($object, $keyOrPropertyOrMethod)) {
×
7603
            return $object->{$keyOrPropertyOrMethod}();
×
7604
        }
7605

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

7609
    /**
7610
     * create a fallback for array
7611
     *
7612
     * 1. use the current array, if it's a array
7613
     * 2. fallback to empty array, if there is nothing
7614
     * 3. call "getArray()" on object, if there is a "Arrayy"-object
7615
     * 4. call "createFromObject()" on object, if there is a "\Traversable"-object
7616
     * 5. call "__toArray()" on object, if the method exists
7617
     * 6. cast a string or object with "__toString()" into an array
7618
     * 7. throw a "InvalidArgumentException"-Exception
7619
     *
7620
     * @param mixed $data
7621
     *
7622
     * @throws \InvalidArgumentException
7623
     *
7624
     * @return array
7625
     *
7626
     * @phpstan-return array<mixed>|array<TKey,T>
7627
     */
7628
    protected function fallbackForArray(&$data): array
7629
    {
7630
        $data = $this->internalGetArray($data);
9,182 ✔
7631

7632
        if ($data === null) {
9,182 ✔
7633
            throw new \InvalidArgumentException('Passed value should be a array');
14 ✔
7634
        }
7635

7636
        return $data;
9,168 ✔
7637
    }
7638

7639
    /**
7640
     * @param bool $preserveKeys <p>
7641
     *                           e.g.: A generator maybe return the same key more than once,
7642
     *                           so maybe you will ignore the keys.
7643
     *                           </p>
7644
     *
7645
     * @return bool
7646
     *
7647
     * @noinspection ReturnTypeCanBeDeclaredInspection
7648
     * @psalm-mutation-free :/
7649
     */
7650
    protected function generatorToArray(bool $preserveKeys = true)
7651
    {
7652
        if ($this->generator) {
8,482 ✔
7653
            $this->array = $this->toArray(false, $preserveKeys);
21 ✔
7654
            $this->generator = null;
21 ✔
7655

7656
            return true;
21 ✔
7657
        }
7658

7659
        return false;
8,482 ✔
7660
    }
7661

7662
    /**
7663
     * Get correct PHP constant for direction.
7664
     *
7665
     * @param int|string $direction
7666
     *
7667
     * @return int
7668
     * @psalm-mutation-free
7669
     */
7670
    protected function getDirection($direction): int
7671
    {
7672
        if ((string) $direction === $direction) {
301 ✔
7673
            $direction = \strtolower($direction);
70 ✔
7674

7675
            if ($direction === 'desc') {
70 ✔
7676
                $direction = \SORT_DESC;
14 ✔
7677
            } else {
7678
                $direction = \SORT_ASC;
63 ✔
7679
            }
7680
        }
7681

7682
        if (
7683
            $direction !== \SORT_DESC
301 ✔
7684
            &&
7685
            $direction !== \SORT_ASC
301 ✔
7686
        ) {
7687
            $direction = \SORT_ASC;
×
7688
        }
7689

7690
        return $direction;
301 ✔
7691
    }
7692

7693
    /**
7694
     * @return TypeCheckInterface[]
7695
     *
7696
     * @noinspection ReturnTypeCanBeDeclaredInspection
7697
     */
7698
    protected function getPropertiesFromPhpDoc()
7699
    {
7700
        static $PROPERTY_CACHE = [];
523 ✔
7701
        static $OPTIONAL_PROPERTY_CACHE = [];
523 ✔
7702
        $cacheKey = 'Class::' . static::class;
523 ✔
7703

7704
        if (isset($PROPERTY_CACHE[$cacheKey])) {
523 ✔
7705
            $this->optionalProperties = $OPTIONAL_PROPERTY_CACHE[$cacheKey] ?? [];
467 ✔
7706

7707
            return $PROPERTY_CACHE[$cacheKey];
467 ✔
7708
        }
7709

7710
        $properties = $this->getPropertiesFromNativeDefinitions();
145 ✔
7711
        $optionalProperties = [];
145 ✔
7712
        $phpDocPropertyAnnotationStyle = null;
145 ✔
7713

7714
        $reflector = new \ReflectionClass($this);
145 ✔
7715
        $factory = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
145 ✔
7716
        $docComment = $reflector->getDocComment();
145 ✔
7717
        if ($docComment) {
145 ✔
7718
            $docblock = $factory->create($docComment);
138 ✔
7719
            $this->addPropertiesFromDocBlock($docblock, $properties, $optionalProperties, $phpDocPropertyAnnotationStyle);
138 ✔
7720
        }
7721

7722
        /** @noinspection PhpAssignmentInConditionInspection */
7723
        while ($reflector = $reflector->getParentClass()) {
138 ✔
7724
            $docComment = $reflector->getDocComment();
138 ✔
7725
            if ($docComment) {
138 ✔
7726
                $docblock = $factory->create($docComment);
138 ✔
7727
                $this->addPropertiesFromDocBlock($docblock, $properties, $optionalProperties, $phpDocPropertyAnnotationStyle);
138 ✔
7728
            }
7729
        }
7730

7731
        $this->optionalProperties = $optionalProperties;
131 ✔
7732
        $OPTIONAL_PROPERTY_CACHE[$cacheKey] = $optionalProperties;
131 ✔
7733

7734
        return $PROPERTY_CACHE[$cacheKey] = $properties;
131 ✔
7735
    }
7736

7737
    /**
7738
     * Merge property definitions from a docblock into the collected property map.
7739
     *
7740
     * @param \phpDocumentor\Reflection\DocBlock $docblock
7741
     * @param TypeCheckInterface[]               $properties
7742
     * @param array<string, true>                $optionalProperties
7743
     * @param 'array-shape'|'property'|null      $phpDocPropertyAnnotationStyle
7744
     *
7745
     * @return void
7746
     */
7747
    private function addPropertiesFromDocBlock($docblock, array &$properties, array &$optionalProperties, ?string &$phpDocPropertyAnnotationStyle): void
7748
    {
7749
        $propertyTags = $docblock->getTagsByName('property');
145 ✔
7750
        $arrayShapeItems = $this->getArrayShapeItemsFromDocBlock($docblock);
145 ✔
7751

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

7756
        $currentPhpDocPropertyAnnotationStyle = null;
138 ✔
7757
        if ($propertyTags !== []) {
138 ✔
7758
            $currentPhpDocPropertyAnnotationStyle = 'property';
49 ✔
7759
        } elseif ($arrayShapeItems !== []) {
138 ✔
7760
            $currentPhpDocPropertyAnnotationStyle = 'array-shape';
56 ✔
7761
        }
7762

7763
        if (
7764
            $currentPhpDocPropertyAnnotationStyle !== null
138 ✔
7765
            &&
7766
            $phpDocPropertyAnnotationStyle !== null
138 ✔
7767
            &&
7768
            $phpDocPropertyAnnotationStyle !== $currentPhpDocPropertyAnnotationStyle
138 ✔
7769
        ) {
7770
            throw new \TypeError('Use either @property tags or array-shape annotations for Arrayy property definitions, not both.');
7 ✔
7771
        }
7772

7773
        if ($currentPhpDocPropertyAnnotationStyle !== null) {
138 ✔
7774
            $phpDocPropertyAnnotationStyle = $currentPhpDocPropertyAnnotationStyle;
98 ✔
7775
        }
7776

7777
        /** @var \phpDocumentor\Reflection\DocBlock\Tags\Property $tag */
7778
        foreach ($propertyTags as $tag) {
138 ✔
7779
            $typeName = $tag->getVariableName();
42 ✔
7780
            /** @var string|null $typeName */
7781
            if (
7782
                $typeName !== null
42 ✔
7783
                &&
7784
                isset($properties[$typeName]) === false
42 ✔
7785
            ) {
7786
                $typeCheckPhpDoc = TypeCheckPhpDoc::fromPhpDocumentorProperty($tag, $typeName);
42 ✔
7787
                if ($typeCheckPhpDoc !== null) {
42 ✔
7788
                    $properties[$typeName] = $typeCheckPhpDoc;
42 ✔
7789
                    unset($optionalProperties[$typeName]);
42 ✔
7790
                }
7791
            }
7792
        }
7793

7794
        foreach ($arrayShapeItems as $item) {
138 ✔
7795
            $typeName = (string) $item->getKey();
56 ✔
7796
            if ($typeName === '') {
56 ✔
7797
                continue;
×
7798
            }
7799

7800
            $typeName = \trim($typeName, '\'"');
56 ✔
7801
            if (isset($properties[$typeName])) {
56 ✔
7802
                continue;
×
7803
            }
7804

7805
            $typeCheckPhpDoc = TypeCheckPhpDoc::fromDocTypeObject($typeName, $item->getValue());
56 ✔
7806
            $properties[$typeName] = $typeCheckPhpDoc;
56 ✔
7807
            if ($item->isOptional()) {
56 ✔
7808
                $optionalProperties[$typeName] = true;
35 ✔
7809
            }
7810
        }
7811
    }
7812

7813
    /**
7814
     * Extract array-shape items from supported @template and @extends annotations.
7815
     *
7816
     * @param \phpDocumentor\Reflection\DocBlock $docblock
7817
     *
7818
     * @return \phpDocumentor\Reflection\PseudoTypes\ArrayShapeItem[]
7819
     */
7820
    private function getArrayShapeItemsFromDocBlock($docblock): array
7821
    {
7822
        if (!\class_exists('\phpDocumentor\Reflection\PseudoTypes\ArrayShape')) {
145 ✔
7823
            return [];
×
7824
        }
7825

7826
        $items = [];
145 ✔
7827
        foreach ($docblock->getTagsByName('template') as $tag) {
145 ✔
7828
            if (
7829
                $tag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Template
145 ✔
7830
                &&
7831
                $tag->getTemplateName() === 'T'
145 ✔
7832
                &&
7833
                $tag->getBound() instanceof \phpDocumentor\Reflection\PseudoTypes\ArrayShape
145 ✔
7834
            ) {
7835
                foreach ($tag->getBound()->getItems() as $item) {
49 ✔
7836
                    $items[] = $item;
49 ✔
7837
                }
7838
            }
7839
        }
7840

7841
        foreach ($docblock->getTagsByName('extends') as $tag) {
145 ✔
7842
            if (!$tag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Extends_) {
145 ✔
7843
                continue;
×
7844
            }
7845

7846
            $type = $tag->getType();
145 ✔
7847
            if (
7848
                !$type instanceof \phpDocumentor\Reflection\PseudoTypes\Generic
145 ✔
7849
                ||
7850
                !$this->isArrayyGenericTarget((string) $type->getFqsen())
145 ✔
7851
            ) {
7852
                continue;
131 ✔
7853
            }
7854

7855
            foreach ($type->getTypes() as $genericType) {
138 ✔
7856
                if ($genericType instanceof \phpDocumentor\Reflection\PseudoTypes\ArrayShape) {
138 ✔
7857
                    foreach ($genericType->getItems() as $item) {
14 ✔
7858
                        $items[] = $item;
14 ✔
7859
                    }
7860
                }
7861
            }
7862
        }
7863

7864
        return $items;
145 ✔
7865
    }
7866

7867
    /**
7868
     * Check whether a generic annotation target is Arrayy, ArrayyStrict, or an Arrayy subclass.
7869
     *
7870
     * @param string $fqcn
7871
     *
7872
     * @return bool
7873
     */
7874
    private function isArrayyGenericTarget(string $fqcn): bool
7875
    {
7876
        $fqcn = \ltrim($fqcn, '\\');
145 ✔
7877
        if ($fqcn === '') {
145 ✔
7878
            return false;
×
7879
        }
7880

7881
        if (\in_array($fqcn, [self::class, ArrayyStrict::class], true)) {
145 ✔
7882
            return true;
138 ✔
7883
        }
7884

7885
        return \class_exists($fqcn) && \is_a($fqcn, self::class, true);
131 ✔
7886
    }
7887

7888
    /**
7889
     * @return TypeCheckInterface[]
7890
     */
7891
    protected function getPropertiesFromNativeDefinitions(): array
7892
    {
7893
        $properties = [];
145 ✔
7894
        $reflector = new \ReflectionClass($this);
145 ✔
7895
        $reservedProperties = self::getReservedPropertyNames();
145 ✔
7896

7897
        do {
7898
            if ($reflector->getName() === self::class) {
145 ✔
7899
                break;
145 ✔
7900
            }
7901

7902
            foreach ($reflector->getProperties() as $property) {
145 ✔
7903
                if (
7904
                    $property->getDeclaringClass()->getName() !== $reflector->getName()
145 ✔
7905
                    ||
7906
                    $property->isStatic()
124 ✔
7907
                    ||
7908
                    isset($reservedProperties[$property->getName()])
124 ✔
7909
                    ||
7910
                    isset($properties[$property->getName()])
145 ✔
7911
                ) {
7912
                    continue;
145 ✔
7913
                }
7914

7915
                $properties[$property->getName()] = TypeCheckPhpDoc::fromReflectionProperty($property);
19 ✔
7916
            }
7917
        } while ($reflector = $reflector->getParentClass());
145 ✔
7918

7919
        return $properties;
145 ✔
7920
    }
7921

7922
    /**
7923
     * @return array<string, true>
7924
     */
7925
    private static function getReservedPropertyNames(): array
7926
    {
7927
        static $reservedProperties = null;
145 ✔
7928

7929
        if ($reservedProperties !== null) {
145 ✔
7930
            return $reservedProperties;
138 ✔
7931
        }
7932

7933
        $reservedProperties = [];
7 ✔
7934
        $reflector = new \ReflectionClass(self::class);
7 ✔
7935
        foreach ($reflector->getProperties() as $property) {
7 ✔
7936
            if ($property->getDeclaringClass()->getName() !== self::class) {
7 ✔
7937
                continue;
×
7938
            }
7939

7940
            $reservedProperties[$property->getName()] = true;
7 ✔
7941
        }
7942

7943
        return $reservedProperties;
7 ✔
7944
    }
7945

7946
    /**
7947
     * @param string $glue
7948
     * @param mixed  $pieces
7949
     * @param bool   $useKeys
7950
     *
7951
     * @return string
7952
     *
7953
     * @phpstan-param scalar|object|self<TKey|T>|array<TKey,T>|array<T> $pieces
7954
     * @psalm-mutation-free
7955
     */
7956
    protected function implode_recursive(
7957
        $glue = '',
7958
        $pieces = [],
7959
        bool $useKeys = false
7960
    ): string {
7961
        if ($pieces instanceof self) {
259 ✔
7962
            $pieces = $pieces->toArray();
7 ✔
7963
        }
7964

7965
        if (\is_array($pieces)) {
259 ✔
7966
            /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
7967
            /** @phpstan-var array<TKey,T> $pieces */
7968
            $pieces = $pieces;
259 ✔
7969

7970
            $pieces_count = \count($pieces, \COUNT_NORMAL);
259 ✔
7971
            $pieces_count_not_zero = $pieces_count > 0;
259 ✔
7972

7973
            return \implode(
259 ✔
7974
                $glue,
259 ✔
7975
                \array_map(
259 ✔
7976
                    [$this, 'implode_recursive'],
259 ✔
7977
                    \array_fill(0, ($pieces_count_not_zero ? $pieces_count : 1), $glue),
259 ✔
7978
                    ($useKeys === true && $pieces_count_not_zero ? $this->array_keys_recursive($pieces) : $pieces)
259 ✔
7979
                )
259 ✔
7980
            );
259 ✔
7981
        }
7982

7983
        if (
7984
            \is_scalar($pieces) === true
259 ✔
7985
            ||
7986
            $pieces instanceof \Stringable
259 ✔
7987
        ) {
7988
            return (string) $pieces;
231 ✔
7989
        }
7990

7991
        return '';
56 ✔
7992
    }
7993

7994
    /**
7995
     * @param mixed                 $needle   <p>
7996
     *                                        The searched value.
7997
     *                                        </p>
7998
     *                                        <p>
7999
     *                                        If needle is a string, the comparison is done
8000
     *                                        in a case-sensitive manner.
8001
     *                                        </p>
8002
     * @param array|\Generator|null $haystack <p>
8003
     *                                        The array.
8004
     *                                        </p>
8005
     * @param bool                  $strict   [optional] <p>
8006
     *                                        If the third parameter strict is set to true
8007
     *                                        then the in_array function will also check the
8008
     *                                        types of the
8009
     *                                        needle in the haystack.
8010
     *                                        </p>
8011
     *
8012
     * @return bool
8013
     *              <p>true if needle is found in the array, false otherwise</p>
8014
     *
8015
     * @phpstan-param array<array-key, mixed>|array<TKey,T>|\Generator<TKey,T>|null $haystack
8016
     *
8017
     * @psalm-mutation-free
8018
     */
8019
    protected function in_array_recursive($needle, $haystack = null, $strict = true): bool
8020
    {
8021
        if ($haystack === null) {
130 ✔
8022
            $haystack = $this->getGenerator();
×
8023
        }
8024

8025
        foreach ($haystack as $item) {
130 ✔
8026
            if (\is_array($item)) {
102 ✔
8027
                $returnTmp = $this->in_array_recursive($needle, $item, $strict);
25 ✔
8028
            } else {
8029
                /** @noinspection NestedPositiveIfStatementsInspection */
8030
                if ($strict === true) {
102 ✔
8031
                    $returnTmp = $item === $needle;
102 ✔
8032
                } else {
8033
                    $returnTmp = $item == $needle;
×
8034
                }
8035
            }
8036

8037
            if ($returnTmp === true) {
102 ✔
8038
                return true;
74 ✔
8039
            }
8040
        }
8041

8042
        return false;
56 ✔
8043
    }
8044

8045
    /**
8046
     * @param mixed $data
8047
     *
8048
     * @return array<mixed>|null
8049
     */
8050
    protected function internalGetArray(&$data)
8051
    {
8052
        if (\is_array($data)) {
9,182 ✔
8053
            return $data;
9,140 ✔
8054
        }
8055

8056
        if (!$data) {
805 ✔
8057
            return [];
49 ✔
8058
        }
8059

8060
        if (\is_object($data) === true) {
798 ✔
8061
            if ($data instanceof \ArrayObject) {
749 ✔
8062
                return $data->getArrayCopy();
35 ✔
8063
            }
8064

8065
            if ($data instanceof \Generator) {
721 ✔
8066
                return static::createFromGeneratorImmutable($data)->toArray();
7 ✔
8067
            }
8068

8069
            if ($data instanceof \Traversable) {
714 ✔
8070
                return static::createFromObject($data)->toArray();
×
8071
            }
8072

8073
            if ($data instanceof \JsonSerializable) {
714 ✔
8074
                return (array) $data->jsonSerialize();
×
8075
            }
8076

8077
            if (\method_exists($data, '__toArray')) {
714 ✔
8078
                return (array) $data->__toArray();
×
8079
            }
8080

8081
            if (\method_exists($data, '__toString')) {
714 ✔
8082
                return [(string) $data];
×
8083
            }
8084
        }
8085

8086
        if (\is_callable($data)) {
763 ✔
8087
            /**
8088
             * @psalm-suppress InvalidPropertyAssignmentValue - why?
8089
             */
8090
            $this->generator = new ArrayyRewindableGenerator($data);
700 ✔
8091

8092
            return [];
700 ✔
8093
        }
8094

8095
        if (\is_scalar($data)) {
77 ✔
8096
            return [$data];
63 ✔
8097
        }
8098

8099
        return null;
14 ✔
8100
    }
8101

8102
    /**
8103
     * Internal mechanics of remove method.
8104
     *
8105
     * @param float|int|string $key
8106
     *
8107
     * @return bool
8108
     */
8109
    protected function internalRemove($key): bool
8110
    {
8111
        $this->generatorToArray();
154 ✔
8112

8113
        if (\is_float($key)) {
154 ✔
NEW
8114
            $key = (int) $key;
×
8115
        }
8116

8117
        if (
8118
            $this->pathSeparator
154 ✔
8119
            &&
8120
            (string) $key === $key
154 ✔
8121
            &&
8122
            \strpos($key, $this->pathSeparator) !== false
154 ✔
8123
        ) {
8124
            $path = \explode($this->pathSeparator, (string) $key);
×
8125
            // crawl though the keys
8126
            while (\count($path, \COUNT_NORMAL) > 1) {
×
8127
                $key = \array_shift($path);
×
8128

8129
                if (!$this->has($key)) {
×
8130
                    return false;
×
8131
                }
8132

8133
                $this->array = &$this->array[$key];
×
8134
            }
8135

8136
            $key = \array_shift($path);
×
8137
        }
8138

8139
        unset($this->array[$key]);
154 ✔
8140

8141
        return true;
154 ✔
8142
    }
8143

8144
    /**
8145
     * Internal mechanic of set method.
8146
     *
8147
     * @param int|string|null $key
8148
     * @param mixed           $value
8149
     * @param bool            $checkProperties
8150
     *
8151
     * @return bool
8152
     *
8153
     * @phpstan-param TKey|null $key
8154
     * @phpstan-param T $value
8155
     */
8156
    protected function internalSet(
8157
        $key,
8158
        &$value,
8159
        bool $checkProperties = true
8160
    ): bool {
8161
        if (
8162
            $checkProperties === true
8,083 ✔
8163
            &&
8164
            $this->properties !== []
8,083 ✔
8165
        ) {
8166
            $this->checkType($key, $value);
1,190 ✔
8167
        }
8168

8169
        if ($key === null) {
8,041 ✔
8170
            return false;
×
8171
        }
8172

8173
        $this->generatorToArray();
8,041 ✔
8174

8175
        $array = &$this->array;
8,041 ✔
8176

8177
        /**
8178
         * https://github.com/vimeo/psalm/issues/2536
8179
         *
8180
         * @psalm-suppress PossiblyInvalidArgument
8181
         * @psalm-suppress InvalidScalarArgument
8182
         */
8183
        if (
8184
            $this->pathSeparator
8,041 ✔
8185
            &&
8186
            (string) $key === $key
8,041 ✔
8187
            &&
8188
            \strpos($key, $this->pathSeparator) !== false
8,041 ✔
8189
        ) {
8190
            $path = \explode($this->pathSeparator, (string) $key);
63 ✔
8191
            // crawl through the keys
8192
            while (\count($path, \COUNT_NORMAL) > 1) {
63 ✔
8193
                $key = \array_shift($path);
63 ✔
8194

8195
                $array = &$array[$key];
63 ✔
8196
            }
8197

8198
            $key = \array_shift($path);
63 ✔
8199
        }
8200

8201
        if ($array === null) {
8,041 ✔
8202
            $array = [];
28 ✔
8203
        } elseif (!\is_array($array)) {
8,020 ✔
8204
            throw new \RuntimeException('Can not set value at this path "' . $key . '" because (' . \gettype($array) . ')"' . \print_r($array, true) . '" is not an array.');
7 ✔
8205
        }
8206

8207
        $array[$key] = $value;
8,041 ✔
8208

8209
        return true;
8,041 ✔
8210
    }
8211

8212
    /**
8213
     * Convert a object into an array.
8214
     *
8215
     * @param mixed|object $object
8216
     *
8217
     * @return array|mixed
8218
     *
8219
     * @psalm-mutation-free
8220
     */
8221
    protected static function objectToArray($object)
8222
    {
8223
        if (!\is_object($object)) {
42 ✔
8224
            return $object;
35 ✔
8225
        }
8226

8227
        $object = \get_object_vars($object);
42 ✔
8228

8229
        /**
8230
         * @psalm-suppress PossiblyInvalidArgument - the parameter is always some kind of array - false-positive from psalm?
8231
         */
8232
        return \array_map([static::class, 'objectToArray'], $object);
42 ✔
8233
    }
8234

8235
    /**
8236
     * @param array $data
8237
     * @param bool  $checkPropertiesInConstructor
8238
     *
8239
     * @return void
8240
     *
8241
     * @phpstan-param array<mixed,T> $data
8242
     */
8243
    protected function setInitialValuesAndProperties(array &$data, bool $checkPropertiesInConstructor)
8244
    {
8245
        $checkPropertiesInConstructor = $this->checkForMissingPropertiesInConstructor === true
9,168 ✔
8246
                                        &&
9,168 ✔
8247
                                        $checkPropertiesInConstructor === true;
9,168 ✔
8248

8249
        if ($this->properties === []) {
9,168 ✔
8250
            if (
8251
                $this->checkPropertyTypes === true
8,475 ✔
8252
                ||
8253
                $checkPropertiesInConstructor === true
8,475 ✔
8254
            ) {
8255
                $this->properties = $this->getPropertiesFromPhpDoc();
495 ✔
8256
            }
8257

8258
            /** @var TypeCheckInterface[] $properties */
8259
            $properties = $this->properties;
8,461 ✔
8260
            $requiredProperties = \array_diff_key($properties, $this->optionalProperties);
8,461 ✔
8261

8262
            if (
8263
                $this->checkPropertiesMismatchInConstructor === true
8,461 ✔
8264
                &&
8265
                \count($data) !== 0
8,461 ✔
8266
                &&
8267
                \count(\array_diff_key($requiredProperties, $data)) > 0
8,461 ✔
8268
            ) {
8269
                throw new \TypeError('Property mismatch - input: ' . \print_r(\array_keys($data), true) . ' | expected: ' . \print_r(\array_keys($requiredProperties), true));
14 ✔
8270
            }
8271
        }
8272

8273
        foreach ($data as $key => &$valueInner) {
9,140 ✔
8274
            $this->internalSet(
7,957 ✔
8275
                $key,
7,957 ✔
8276
                $valueInner,
7,957 ✔
8277
                $checkPropertiesInConstructor
7,957 ✔
8278
            );
7,957 ✔
8279
        }
8280
    }
8281

8282
    /**
8283
     * sorting keys
8284
     *
8285
     * @param array      $elements
8286
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
8287
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
8288
     *                              <strong>SORT_NATURAL</strong></p>
8289
     *
8290
     * @return $this
8291
     *               <p>(Mutable) Return this Arrayy object.</p>
8292
     *
8293
     * @phpstan-param  array<mixed|TKey,T> $elements
8294
     * @phpstan-return static
8295
     */
8296
    protected function sorterKeys(
8297
        array &$elements,
8298
        $direction = \SORT_ASC,
8299
        int $strategy = \SORT_REGULAR
8300
    ): self {
8301
        $direction = $this->getDirection($direction);
126 ✔
8302

8303
        switch ($direction) {
8304
            case 'desc':
126 ✔
8305
            case \SORT_DESC:
8306
                \krsort($elements, $strategy);
42 ✔
8307

8308
                break;
42 ✔
8309
            case 'asc':
91 ✔
8310
            case \SORT_ASC:
91 ✔
8311
            default:
8312
                \ksort($elements, $strategy);
91 ✔
8313
        }
8314

8315
        return $this;
126 ✔
8316
    }
8317

8318
    /**
8319
     * @param array      $elements  <p>Warning: used as reference</p>
8320
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
8321
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
8322
     *                              <strong>SORT_NATURAL</strong></p>
8323
     * @param bool       $keepKeys
8324
     *
8325
     * @return $this
8326
     *               <p>(Mutable) Return this Arrayy object.</p>
8327
     *
8328
     * @phpstan-param array<mixed|TKey,T> $elements
8329
     * @phpstan-return static
8330
     */
8331
    protected function sorting(
8332
        array &$elements,
8333
        $direction = \SORT_ASC,
8334
        int $strategy = \SORT_REGULAR,
8335
        bool $keepKeys = false
8336
    ): self {
8337
        $direction = $this->getDirection($direction);
168 ✔
8338

8339
        if (!$strategy) {
168 ✔
8340
            $strategy = \SORT_REGULAR;
168 ✔
8341
        }
8342

8343
        switch ($direction) {
8344
            case 'desc':
168 ✔
8345
            case \SORT_DESC:
8346
                if ($keepKeys) {
91 ✔
8347
                    \arsort($elements, $strategy);
63 ✔
8348
                } else {
8349
                    \rsort($elements, $strategy);
28 ✔
8350
                }
8351

8352
                break;
91 ✔
8353
            case 'asc':
77 ✔
8354
            case \SORT_ASC:
77 ✔
8355
            default:
8356
                if ($keepKeys) {
77 ✔
8357
                    \asort($elements, $strategy);
28 ✔
8358
                } else {
8359
                    \sort($elements, $strategy);
49 ✔
8360
                }
8361
        }
8362

8363
        return $this;
168 ✔
8364
    }
8365

8366
    /**
8367
     * @param array $array
8368
     *
8369
     * @return array
8370
     *
8371
     * @phpstan-param array<array-key, mixed> $array
8372
     * @phpstan-return array<array-key, mixed>
8373
     *
8374
     * @psalm-mutation-free
8375
     */
8376
    private function getArrayRecursiveHelperArrayy(array $array)
8377
    {
8378
        if ($array === []) {
175 ✔
8379
            return [];
×
8380
        }
8381

8382
        \array_walk_recursive(
175 ✔
8383
            $array,
175 ✔
8384
            /**
8385
             * @param array|self $item
8386
             *
8387
             * @return void
8388
             */
8389
            static function (&$item) {
175 ✔
8390
                if ($item instanceof self) {
175 ✔
8391
                    $item = $item->getArray();
7 ✔
8392
                }
8393
            }
175 ✔
8394
        );
175 ✔
8395

8396
        return $array;
175 ✔
8397
    }
8398

8399
    /**
8400
     * @param int|string|null $key
8401
     * @param mixed           $value
8402
     *
8403
     * @return void
8404
     */
8405
    private function checkType($key, $value)
8406
    {
8407
        if (
8408
            $key !== null
1,190 ✔
8409
            &&
8410
            isset($this->properties[$key]) === false
1,190 ✔
8411
            &&
8412
            $this->checkPropertiesMismatch === true
1,190 ✔
8413
        ) {
8414
            throw new \TypeError('The key "' . $key . '" does not exist as a property definition. (' . \get_class($this) . ').');
28 ✔
8415
        }
8416

8417
        if (isset($this->properties[self::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES])) {
1,190 ✔
8418
            $this->properties[self::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES]->checkType($value);
833 ✔
8419
        } elseif ($key !== null && isset($this->properties[$key])) {
434 ✔
8420
            $this->properties[$key]->checkType($value);
434 ✔
8421
        }
8422
    }
8423
}
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