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

voku / Arrayy / 24920239353

25 Apr 2026 02:17AM UTC coverage: 92.192% (+2.9%) from 89.25%
24920239353

Pull #162

github

web-flow
Merge 88bedb0d1 into e1b421950
Pull Request #162: Increase regression coverage for Json mapper, collection helpers, and array-shape property contracts

82 of 89 new or added lines in 3 files covered. (92.13%)

1 existing line in 1 file now uncovered.

2751 of 2984 relevant lines covered (92.19%)

249.28 hits per line

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

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

3
declare(strict_types=1);
4

5
namespace Arrayy;
6

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

175
        return $return;
×
176
    }
177

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

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

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

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

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

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

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

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

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

286
            return $this;
28 ✔
287
        }
288

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

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

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

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

332
        return $this;
140 ✔
333
    }
334

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

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

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

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

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

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

402
        return $this;
28 ✔
403
    }
404

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

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

429
        return $that;
28 ✔
430
    }
431

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

483
        return \count($this->toArray(), $mode);
1,022 ✔
484
    }
485

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

509
        $this->array = $array;
7 ✔
510
        $this->generator = null;
7 ✔
511

512
        return $this->array;
7 ✔
513
    }
514

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

526
        return $this->array;
42 ✔
527
    }
528

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

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

554
            $this->generator = $generatorTmp;
14 ✔
555

556
            return $this->generator;
14 ✔
557
        }
558

559
        $iterator = $this->getIteratorClass();
238 ✔
560

561
        if ($iterator === ArrayyIterator::class) {
238 ✔
562
            return new $iterator($this->toArray(), 0, static::class);
238 ✔
563
        }
564

565
        $return = new $iterator($this->toArray());
×
566
        \assert($return instanceof \Iterator);
567

568
        return $return;
×
569
    }
570

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

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

602
        \ksort($this->array, $sort_flags);
28 ✔
603

604
        return $this;
28 ✔
605
    }
606

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

625
        /**
626
         * @psalm-suppress ImpureMethodCall - object is already cloned
627
         */
628
        $that->ksort($sort_flags);
28 ✔
629

630
        return $that;
28 ✔
631
    }
632

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

646
        \natcasesort($this->array);
56 ✔
647

648
        return $this;
56 ✔
649
    }
650

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

664
        /**
665
         * @psalm-suppress ImpureMethodCall - object is already cloned
666
         */
667
        $that->natcasesort();
28 ✔
668

669
        return $that;
28 ✔
670
    }
671

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

685
        \natsort($this->array);
70 ✔
686

687
        return $this;
70 ✔
688
    }
689

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

703
        /**
704
         * @psalm-suppress ImpureMethodCall - object is already cloned
705
         */
706
        $that->natsort();
28 ✔
707

708
        return $that;
28 ✔
709
    }
710

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

729
        $offsetExists = $this->keyExists($offset);
1,377 ✔
730
        if ($offsetExists === true) {
1,377 ✔
731
            return true;
1,230 ✔
732
        }
733

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

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

766
        return $offsetExists;
973 ✔
767
    }
768

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

787
        if ($this->offsetExists($offset)) {
1,160 ✔
788
            /** @phpstan-ignore-next-line */
789
            $value = &$this->__get($offset);
1,146 ✔
790
        }
791

792
        /** @phpstan-ignore-next-line */
793
        return $value;
1,160 ✔
794
    }
795

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

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

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

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

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

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

844
            return;
98 ✔
845
        }
846

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

862
            if ($path !== false) {
49 ✔
863
                $pathToUnset = \array_pop($path);
49 ✔
864

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

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

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

898
        if (\PHP_VERSION_ID < 70400) {
7 ✔
899
            return parent::serialize();
×
900
        }
901

902
        return \serialize($this);
7 ✔
903
    }
904

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

922
            return;
8,916 ✔
923
        }
924

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

934
                return;
×
935
            }
936
        }
937

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

941
    /**
942
     * Sort the entries with a user-defined comparison function and maintain key association.
943
     *
944
     * @param callable $callable
945
     *
946
     *@throws \InvalidArgumentException
947
     *
948
     * @return $this
949
     *               <p>(Mutable) Return this Arrayy object.</p>
950
     *
951
     * @phpstan-param  callable(T,T):int $callable
952
     * @phpstan-return static
953
     */
954
    #[\ReturnTypeWillChange]
955
    public function uasort($callable): self
956
    {
957
        if (!\is_callable($callable)) {
56 ✔
958
            throw new \InvalidArgumentException('Passed function must be callable');
×
959
        }
960

961
        $this->generatorToArray();
56 ✔
962

963
        \uasort($this->array, $callable);
56 ✔
964

965
        return $this;
56 ✔
966
    }
967

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

986
        /**
987
         * @psalm-suppress ImpureMethodCall - object is already cloned
988
         */
989
        $that->uasort($callable);
28 ✔
990

991
        return $that;
28 ✔
992
    }
993

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

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

1032
    /**
1033
     * Unserialize an string and return the instance of the "Arrayy"-class.
1034
     *
1035
     * EXAMPLE: <code>
1036
     * $serialized = a([1, 4, 7])->serialize();
1037
     * a()->unserialize($serialized);
1038
     * </code>
1039
     *
1040
     * @param string $string
1041
     *
1042
     * @return $this
1043
     *
1044
     * @phpstan-return static
1045
     */
1046
    #[\ReturnTypeWillChange]
1047
    public function unserialize($string): self
1048
    {
1049
        if (\PHP_VERSION_ID < 70400) {
7 ✔
1050
            parent::unserialize($string);
×
1051

1052
            return $this;
×
1053
        }
1054

1055
        return \unserialize($string, ['allowed_classes' => [__CLASS__, TypeCheckPhpDoc::class]]);
7 ✔
1056
    }
1057

1058
    /**
1059
     * Append a (key) + values to the current array.
1060
     *
1061
     * EXAMPLE: <code>
1062
     * a(['fΓ²Γ΄' => ['bΓ Ε™']])->appendArrayValues(['foo1', 'foo2'], 'fΓ²Γ΄'); // Arrayy['fΓ²Γ΄' => ['bΓ Ε™', 'foo1', 'foo2']]
1063
     * </code>
1064
     *
1065
     * @param array $values
1066
     * @param mixed $key
1067
     *
1068
     * @return $this
1069
     *               <p>(Mutable) Return this Arrayy object, with the appended values.</p>
1070
     *
1071
     * @phpstan-param  array<T> $values
1072
     * @phpstan-param  TKey|null $key
1073
     * @phpstan-return static
1074
     */
1075
    public function appendArrayValues(array $values, $key = null)
1076
    {
1077
        $this->generatorToArray();
7 ✔
1078

1079
        if ($key !== null) {
7 ✔
1080
            if (
1081
                isset($this->array[$key])
7 ✔
1082
                &&
1083
                \is_array($this->array[$key])
7 ✔
1084
            ) {
1085
                foreach ($values as $value) {
7 ✔
1086
                    $this->array[$key][] = $value;
7 ✔
1087
                }
1088
            } else {
UNCOV
1089
                foreach ($values as $value) {
3 ✔
1090
                    $this->array[$key] = $value;
×
1091
                }
1092
            }
1093
        } else {
1094
            foreach ($values as $value) {
×
1095
                $this->array[] = $value;
×
1096
            }
1097
        }
1098

1099
        return $this;
7 ✔
1100
    }
1101

1102
    /**
1103
     * Add a suffix to each key.
1104
     *
1105
     * @param int|string $prefix
1106
     *
1107
     * @return static
1108
     *                <p>(Immutable) Return an Arrayy object, with the prefixed keys.</p>
1109
     *
1110
     * @phpstan-return static
1111
     * @psalm-mutation-free
1112
     */
1113
    public function appendToEachKey($prefix): self
1114
    {
1115
        // init
1116
        $result = [];
70 ✔
1117

1118
        foreach ($this->getGenerator() as $key => $item) {
70 ✔
1119
            if ($item instanceof self) {
63 ✔
1120
                $result[$prefix . $key] = $item->appendToEachKey($prefix);
×
1121
            } elseif (\is_array($item)) {
63 ✔
1122
                $result[$prefix . $key] = self::create($item, $this->iteratorClass, false)
×
1123
                    ->appendToEachKey($prefix)
×
1124
                    ->toArray();
×
1125
            } else {
1126
                $result[$prefix . $key] = $item;
63 ✔
1127
            }
1128
        }
1129

1130
        return self::create(
70 ✔
1131
            $result,
70 ✔
1132
            $this->iteratorClass,
70 ✔
1133
            false
70 ✔
1134
        );
70 ✔
1135
    }
1136

1137
    /**
1138
     * Add a prefix to each value.
1139
     *
1140
     * @param float|int|string $prefix
1141
     *
1142
     * @return static
1143
     *                <p>(Immutable) Return an Arrayy object, with the prefixed values.</p>
1144
     *
1145
     * @phpstan-return static
1146
     * @psalm-mutation-free
1147
     */
1148
    public function appendToEachValue($prefix): self
1149
    {
1150
        // init
1151
        $result = [];
70 ✔
1152

1153
        foreach ($this->getGenerator() as $key => $item) {
70 ✔
1154
            if ($item instanceof self) {
63 ✔
1155
                $result[$key] = $item->appendToEachValue($prefix);
×
1156
            } elseif (\is_array($item)) {
63 ✔
1157
                $result[$key] = self::create($item, $this->iteratorClass, false)->appendToEachValue($prefix)->toArray();
×
1158
            } elseif (\is_object($item) === true) {
63 ✔
1159
                $result[$key] = $item;
7 ✔
1160
            } else {
1161
                $result[$key] = $prefix . $item;
56 ✔
1162
            }
1163
        }
1164

1165
        return self::create($result, $this->iteratorClass, false);
70 ✔
1166
    }
1167

1168
    /**
1169
     * Sort an array in reverse order and maintain index association.
1170
     *
1171
     * @return $this
1172
     *               <p>(Mutable) Return this Arrayy object.</p>
1173
     *
1174
     * @phpstan-return static
1175
     */
1176
    public function arsort(): self
1177
    {
1178
        $this->generatorToArray();
28 ✔
1179

1180
        \arsort($this->array);
28 ✔
1181

1182
        return $this;
28 ✔
1183
    }
1184

1185
    /**
1186
     * Sort an array in reverse order and maintain index association.
1187
     *
1188
     * @return $this
1189
     *               <p>(Immutable) Return this Arrayy object.</p>
1190
     *
1191
     * @phpstan-return static
1192
     * @psalm-mutation-free
1193
     */
1194
    public function arsortImmutable(): self
1195
    {
1196
        $that = clone $this;
70 ✔
1197

1198
        $that->generatorToArray();
70 ✔
1199

1200
        \arsort($that->array);
70 ✔
1201

1202
        return $that;
70 ✔
1203
    }
1204

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

1229
        foreach ($that->getGenerator() as $key => $value) {
21 ✔
1230
            $closure($value, $key);
21 ✔
1231
        }
1232

1233
        return static::create(
21 ✔
1234
            $that->toArray(),
21 ✔
1235
            $this->iteratorClass,
21 ✔
1236
            false
21 ✔
1237
        );
21 ✔
1238
    }
1239

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

1258
        if (!$count) {
70 ✔
1259
            return 0;
14 ✔
1260
        }
1261

1262
        if ((int) $decimals !== $decimals) {
56 ✔
1263
            $decimals = 0;
21 ✔
1264
        }
1265

1266
        $sum = 0;
56 ✔
1267
        foreach ($array as $value) {
56 ✔
1268
            if (
1269
                \is_int($value)
56 ✔
1270
                ||
1271
                \is_float($value)
49 ✔
1272
                ||
1273
                \is_bool($value)
56 ✔
1274
            ) {
1275
                $sum += $value;
42 ✔
1276
            } elseif (\is_string($value) && \is_numeric($value)) {
14 ✔
1277
                $sum += (float) $value;
×
1278
            }
1279
        }
1280

1281
        return \round($sum / $count, $decimals);
56 ✔
1282
    }
1283

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

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

1322
            $return[$key] = $value;
7 ✔
1323
        }
1324

1325
        return static::create(
7 ✔
1326
            $return,
7 ✔
1327
            $this->iteratorClass,
7 ✔
1328
            false
7 ✔
1329
        );
7 ✔
1330
    }
1331

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

1348
        return $this;
77 ✔
1349
    }
1350

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

1376
                    $values[$key] = $value;
×
1377
                    if ($tmpCounter === $size) {
×
1378
                        yield $values;
×
1379

1380
                        $values = [];
×
1381
                        $tmpCounter = 0;
×
1382
                    }
1383
                }
1384

1385
                if ($values !== []) {
×
1386
                    yield $values;
×
1387
                }
1388
            };
×
1389
        } else {
1390
            $generator = function () use ($size) {
42 ✔
1391
                $values = [];
42 ✔
1392
                $tmpCounter = 0;
42 ✔
1393
                foreach ($this->getGenerator() as $value) {
42 ✔
1394
                    ++$tmpCounter;
42 ✔
1395

1396
                    $values[] = $value;
42 ✔
1397
                    if ($tmpCounter === $size) {
42 ✔
1398
                        yield $values;
42 ✔
1399

1400
                        $values = [];
42 ✔
1401
                        $tmpCounter = 0;
42 ✔
1402
                    }
1403
                }
1404

1405
                if ($values !== []) {
42 ✔
1406
                    yield $values;
35 ✔
1407
                }
1408
            };
42 ✔
1409
        }
1410

1411
        return static::create(
42 ✔
1412
            $generator,
42 ✔
1413
            $this->iteratorClass,
42 ✔
1414
            false
42 ✔
1415
        );
42 ✔
1416
    }
1417

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

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

1465
            return $this;
21 ✔
1466
        }
1467

1468
        $this->array = [];
49 ✔
1469
        $this->generator = null;
49 ✔
1470

1471
        return $this;
49 ✔
1472
    }
1473

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

1494
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1495
        $tmpCount = 0;
70 ✔
1496
        foreach ($this->getGeneratorByReference() as &$valueFromArray) {
70 ✔
1497
            $tmpCount++;
56 ✔
1498

1499
            if ($strict) {
56 ✔
1500
                if ($value !== $valueFromArray) {
56 ✔
1501
                    return false;
36 ✔
1502
                }
1503
            } else {
1504
                /** @noinspection NestedPositiveIfStatementsInspection */
1505
                if ($value != $valueFromArray) {
×
1506
                    return false;
×
1507
                }
1508
            }
1509
        }
1510

1511
        return $tmpCount !== 0;
49 ✔
1512
    }
1513

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

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

1548
        return false;
49 ✔
1549
    }
1550

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

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

1585
            return false;
56 ✔
1586
        }
1587

1588
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1589
        foreach ($this->getGeneratorByReference() as &$valueTmp) {
84 ✔
1590
            if (\mb_strtoupper((string) $valueTmp) === \mb_strtoupper((string) $value)) {
77 ✔
1591
                return true;
56 ✔
1592
            }
1593
        }
1594

1595
        return false;
28 ✔
1596
    }
1597

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

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

1651
        return \count(
7 ✔
1652
            \array_intersect($needles, $this->keys()->toArray()),
7 ✔
1653
            \COUNT_NORMAL
7 ✔
1654
        )
7 ✔
1655
                ===
7 ✔
1656
                \count(
7 ✔
1657
                    $needles,
7 ✔
1658
                    \COUNT_NORMAL
7 ✔
1659
                );
7 ✔
1660
    }
1661

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

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

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

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

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

1759
        return $return;
49 ✔
1760
    }
1761

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

1788
        return $instance;
5,468 ✔
1789
    }
1790

1791
    /**
1792
     * Flatten an array with the given character as a key delimiter.
1793
     *
1794
     * EXAMPLE: <code>
1795
     * $dot = a(['foo' => ['abc' => 'xyz', 'bar' => ['baz']]]);
1796
     * $flatten = $dot->flatten();
1797
     * $flatten['foo.abc']; // 'xyz'
1798
     * $flatten['foo.bar.0']; // 'baz'
1799
     * </code>
1800
     *
1801
     * @param string     $delimiter
1802
     * @param string     $prepend
1803
     * @param array|null $items
1804
     *
1805
     * @return array
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;
197 ✔
1847
        $this->generator = null;
197 ✔
1848

1849
        return $this;
197 ✔
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
        return self::create(\iterator_to_array($generator, true));
28 ✔
1884
    }
1885

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

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

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

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

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

1949
        return $arrayy;
28 ✔
1950
    }
1951

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

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

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

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

2012
        /** @var static $return - help for phpstan */
2013
        $return = static::create($array);
70 ✔
2014

2015
        return $return;
70 ✔
2016
    }
2017

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

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

2056
        return $return;
14 ✔
2057
    }
2058

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

2072
        return \current($this->array);
×
2073
    }
2074

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

2105
        \uksort($this->array, $callable);
35 ✔
2106

2107
        return $this;
35 ✔
2108
    }
2109

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

2130
        $that->generatorToArray();
7 ✔
2131

2132
        /**
2133
         * @psalm-suppress ImpureFunctionCall - object is already cloned
2134
         */
2135
        \uksort($that->array, $callable);
7 ✔
2136

2137
        return $that;
7 ✔
2138
    }
2139

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

2168
        \usort($this->array, $callable);
77 ✔
2169

2170
        return $this;
77 ✔
2171
    }
2172

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

2193
        /**
2194
         * @psalm-suppress ImpureMethodCall - object is already cloned
2195
         */
2196
        $that->customSortValues($callable);
35 ✔
2197

2198
        return $that;
35 ✔
2199
    }
2200

2201
    /**
2202
     * Delete the given key or keys.
2203
     *
2204
     * @param int|int[]|string|string[] $keyOrKeys
2205
     *
2206
     * @return void
2207
     */
2208
    public function delete($keyOrKeys)
2209
    {
2210
        $keyOrKeys = (array) $keyOrKeys;
63 ✔
2211

2212
        foreach ($keyOrKeys as $key) {
63 ✔
2213
            $this->offsetUnset($key);
63 ✔
2214
        }
2215
    }
2216

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

2241
        $generator = function () use ($array): \Generator {
91 ✔
2242
            foreach ($this->getGenerator() as $key => $value) {
91 ✔
2243
                if (\in_array($value, $array, true) === false) {
77 ✔
2244
                    yield $key => $value;
35 ✔
2245
                }
2246
            }
2247
        };
91 ✔
2248

2249
        return static::create(
91 ✔
2250
            $generator,
91 ✔
2251
            $this->iteratorClass,
91 ✔
2252
            false
91 ✔
2253
        );
91 ✔
2254
    }
2255

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

2276
        $generator = function () use ($array): \Generator {
63 ✔
2277
            foreach ($this->getGenerator() as $key => $value) {
63 ✔
2278
                if (\array_key_exists($key, $array) === false) {
56 ✔
2279
                    yield $key => $value;
14 ✔
2280
                }
2281
            }
2282
        };
63 ✔
2283

2284
        return static::create(
63 ✔
2285
            $generator,
63 ✔
2286
            $this->iteratorClass,
63 ✔
2287
            false
63 ✔
2288
        );
63 ✔
2289
    }
2290

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

2311
        $generator = function () use ($array): \Generator {
63 ✔
2312
            foreach ($this->getGenerator() as $key => $value) {
63 ✔
2313
                $isset = isset($array[$key]);
56 ✔
2314

2315
                if (
2316
                    !$isset
56 ✔
2317
                    ||
2318
                    $array[$key] !== $value
56 ✔
2319
                ) {
2320
                    yield $key => $value;
28 ✔
2321
                }
2322
            }
2323
        };
63 ✔
2324

2325
        return static::create(
63 ✔
2326
            $generator,
63 ✔
2327
            $this->iteratorClass,
63 ✔
2328
            false
63 ✔
2329
        );
63 ✔
2330
    }
2331

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

2355
        if (
2356
            $helperVariableForRecursion !== null
7 ✔
2357
            &&
2358
            \is_array($helperVariableForRecursion)
7 ✔
2359
        ) {
2360
            $arrayForTheLoop = $helperVariableForRecursion;
×
2361
        } else {
2362
            $arrayForTheLoop = $this->getGenerator();
7 ✔
2363
        }
2364

2365
        foreach ($arrayForTheLoop as $key => $value) {
7 ✔
2366
            if ($value instanceof self) {
7 ✔
2367
                $value = $value->toArray();
7 ✔
2368
            }
2369

2370
            if (\array_key_exists($key, $array)) {
7 ✔
2371
                if ($value !== $array[$key]) {
7 ✔
2372
                    $result[$key] = $value;
7 ✔
2373
                }
2374
            } else {
2375
                $result[$key] = $value;
7 ✔
2376
            }
2377
        }
2378

2379
        return static::create(
7 ✔
2380
            $result,
7 ✔
2381
            $this->iteratorClass,
7 ✔
2382
            false
7 ✔
2383
        );
7 ✔
2384
    }
2385

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

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

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

2461
        foreach ($this->getGenerator() as $key => $value) {
42 ✔
2462
            $array[$key] = $closure($value, $key);
42 ✔
2463
        }
2464

2465
        return static::create(
42 ✔
2466
            $array,
42 ✔
2467
            $this->iteratorClass,
42 ✔
2468
            false
42 ✔
2469
        );
42 ✔
2470
    }
2471

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

2487
            $counter = 0;
×
2488
            /** @noinspection PhpUnusedLocalVariableInspection */
2489
            foreach ($this->getIterator() as $item) {
×
2490
                if (++$counter === $count - 1) {
×
2491
                    break;
×
2492
                }
2493
            }
2494
        }
2495

2496
        return \end($this->array);
×
2497
    }
2498

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

2521
        foreach ($this->getGenerator() as $key => $value) {
28 ✔
2522
            if ($closure($value, $key)) {
21 ✔
2523
                $isExists = true;
7 ✔
2524

2525
                break;
7 ✔
2526
            }
2527
        }
2528

2529
        return $isExists;
28 ✔
2530
    }
2531

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

2555
        $this->generatorToArray();
49 ✔
2556

2557
        $tmpArray = $this->array;
49 ✔
2558

2559
        $count = \count($tmpArray);
49 ✔
2560

2561
        while ($count < $num) {
49 ✔
2562
            $tmpArray[] = $default;
28 ✔
2563
            ++$count;
28 ✔
2564
        }
2565

2566
        return static::create(
49 ✔
2567
            $tmpArray,
49 ✔
2568
            $this->iteratorClass,
49 ✔
2569
            false
49 ✔
2570
        );
49 ✔
2571
    }
2572

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

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

2632
            $generator = function () use ($closure) {
91 ✔
2633
                foreach ($this->getGenerator() as $key => $value) {
84 ✔
2634
                    if ($closure($value, $key) === true) {
77 ✔
2635
                        yield $key => $value;
70 ✔
2636
                    }
2637
                }
2638
            };
91 ✔
2639
        } else {
2640
            $generator = function () use ($closure) {
7 ✔
2641
                foreach ($this->getGenerator() as $key => $value) {
7 ✔
2642
                    if ($closure($value) === true) {
7 ✔
2643
                        yield $key => $value;
7 ✔
2644
                    }
2645
                }
2646
            };
7 ✔
2647
        }
2648

2649
        return static::create(
91 ✔
2650
            $generator,
91 ✔
2651
            $this->iteratorClass,
91 ✔
2652
            false
91 ✔
2653
        );
91 ✔
2654
    }
2655

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

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

2734
        $result = \array_values(
7 ✔
2735
            \array_filter(
7 ✔
2736
                $this->toArray(false, true),
7 ✔
2737
                static function ($item) use (
7 ✔
2738
                    $property,
7 ✔
2739
                    $value,
7 ✔
2740
                    $ops,
7 ✔
2741
                    $comparisonOp
7 ✔
2742
                ) {
7 ✔
2743
                    $item = (array) $item;
7 ✔
2744
                    $itemArrayy = static::create($item);
7 ✔
2745
                    $item[$property] = $itemArrayy->get($property, []);
7 ✔
2746

2747
                    return $ops[$comparisonOp]($item, $property, $value);
7 ✔
2748
                }
7 ✔
2749
            )
7 ✔
2750
        );
7 ✔
2751

2752
        return static::create(
7 ✔
2753
            $result,
7 ✔
2754
            $this->iteratorClass,
7 ✔
2755
            false
7 ✔
2756
        );
7 ✔
2757
    }
2758

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

2786
        return false;
21 ✔
2787
    }
2788

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

2816
        return false;
28 ✔
2817
    }
2818

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

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

2866
        return $this->get($key_first);
134 ✔
2867
    }
2868

2869
    /**
2870
     * Get the first key from the current array.
2871
     *
2872
     * @return mixed|null
2873
     *                    <p>Return null if there wasn't a element.</p>
2874
     *
2875
     * @phpstan-return TKey|null
2876
     *
2877
     * @psalm-mutation-free
2878
     */
2879
    public function firstKey()
2880
    {
2881
        $this->generatorToArray();
204 ✔
2882

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

2886
        return $return;
204 ✔
2887
    }
2888

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

2909
        if ($number === null) {
259 ✔
2910
            $array = (array) \array_shift($arrayTmp);
98 ✔
2911
        } else {
2912
            $array = \array_splice($arrayTmp, 0, $number);
161 ✔
2913
        }
2914

2915
        return static::create(
259 ✔
2916
            $array,
259 ✔
2917
            $this->iteratorClass,
259 ✔
2918
            false
259 ✔
2919
        );
259 ✔
2920
    }
2921

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

2938
        if ($number === null) {
21 ✔
2939
            $array = (array) \array_shift($arrayTmp);
×
2940
        } else {
2941
            $array = \array_splice($arrayTmp, 0, $number);
21 ✔
2942
        }
2943

2944
        return static::create(
21 ✔
2945
            $array,
21 ✔
2946
            $this->iteratorClass,
21 ✔
2947
            false
21 ✔
2948
        );
21 ✔
2949
    }
2950

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

2970
        if ($number === null) {
238 ✔
2971
            $shift = \array_shift($this->array);
133 ✔
2972
            /* @phpstan-ignore-next-line | I am not sure if "array<int, T of mixed>" is an error here? */
2973
            $this->array = $shift !== null ? [$shift] : [];
133 ✔
2974
        } else {
2975
            $splice = \array_splice($this->array, 0, $number);
105 ✔
2976
            $this->array = $splice;
105 ✔
2977
        }
2978

2979
        return $this;
238 ✔
2980
    }
2981

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

3003
        return static::create(
7 ✔
3004
            $generator,
7 ✔
3005
            $this->iteratorClass,
7 ✔
3006
            false
7 ✔
3007
        );
7 ✔
3008
    }
3009

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

3051
            return clone $this;
7 ✔
3052
        }
3053

3054
        if ($array !== null) {
1,979 ✔
3055
            if ($useByReference) {
28 ✔
3056
                $usedArray = &$array;
×
3057
            } else {
3058
                $usedArray = $array;
28 ✔
3059
            }
3060
        } else {
3061
            $this->generatorToArray();
1,958 ✔
3062

3063
            if ($useByReference) {
1,958 ✔
3064
                $usedArray = &$this->array;
1,167 ✔
3065
            } else {
3066
                $usedArray = $this->array;
918 ✔
3067
            }
3068
        }
3069

3070
        if ($key === null) {
1,979 ✔
3071
            return static::create(
7 ✔
3072
                [],
7 ✔
3073
                $this->iteratorClass,
7 ✔
3074
                false
7 ✔
3075
            )->createByReference($usedArray);
7 ✔
3076
        }
3077

3078
        // php cast "bool"-index into "int"-index
3079
        /* @phpstan-ignore-next-line | this is only a fallback */
3080
        if ((bool) $key === $key) {
1,979 ✔
3081
            $key = (int) $key;
×
3082
        }
3083

3084
        if (\array_key_exists($key, $usedArray) === true) {
1,979 ✔
3085
            if (\is_array($usedArray[$key])) {
1,713 ✔
3086
                return static::create(
141 ✔
3087
                    [],
141 ✔
3088
                    $this->iteratorClass,
141 ✔
3089
                    false
141 ✔
3090
                )->createByReference($usedArray[$key]);
141 ✔
3091
            }
3092

3093
            return $usedArray[$key];
1,601 ✔
3094
        }
3095

3096
        // crawl through array, get key according to object or not
3097
        $usePath = false;
427 ✔
3098
        if (
3099
            $this->pathSeparator
427 ✔
3100
            &&
3101
            (string) $key === $key
427 ✔
3102
            &&
3103
            \strpos($key, $this->pathSeparator) !== false
427 ✔
3104
        ) {
3105
            $segments = \explode($this->pathSeparator, (string) $key);
217 ✔
3106
            if ($segments !== false) {
217 ✔
3107
                $usePath = true;
217 ✔
3108
                $usedArrayTmp = $usedArray; // do not use the reference for dot-annotations
217 ✔
3109

3110
                foreach ($segments as $segment) {
217 ✔
3111
                    if (
3112
                        (
3113
                            \is_array($usedArrayTmp)
217 ✔
3114
                            ||
217 ✔
3115
                            $usedArrayTmp instanceof \ArrayAccess
217 ✔
3116
                        )
3117
                        &&
3118
                        isset($usedArrayTmp[$segment])
217 ✔
3119
                    ) {
3120
                        $usedArrayTmp = $usedArrayTmp[$segment];
210 ✔
3121

3122
                        continue;
210 ✔
3123
                    }
3124

3125
                    if (
3126
                        \is_object($usedArrayTmp) === true
98 ✔
3127
                        &&
3128
                        \property_exists($usedArrayTmp, $segment)
98 ✔
3129
                    ) {
3130
                        $usedArrayTmp = $usedArrayTmp->{$segment};
7 ✔
3131

3132
                        continue;
7 ✔
3133
                    }
3134

3135
                    if (isset($segments[0]) && $segments[0] === '*') {
91 ✔
3136
                        $segmentsTmp = $segments;
7 ✔
3137
                        unset($segmentsTmp[0]);
7 ✔
3138
                        $keyTmp = \implode('.', $segmentsTmp);
7 ✔
3139
                        $returnTmp = static::create(
7 ✔
3140
                            [],
7 ✔
3141
                            $this->iteratorClass,
7 ✔
3142
                            false
7 ✔
3143
                        );
7 ✔
3144
                        foreach ($this->getAll() as $dataTmp) {
7 ✔
3145
                            if ($dataTmp instanceof self) {
7 ✔
3146
                                $returnTmp->add($dataTmp->get($keyTmp));
×
3147

3148
                                continue;
×
3149
                            }
3150

3151
                            if (
3152
                                (
3153
                                    \is_array($dataTmp)
7 ✔
3154
                                    ||
7 ✔
3155
                                    $dataTmp instanceof \ArrayAccess
7 ✔
3156
                                )
3157
                                &&
3158
                                isset($dataTmp[$keyTmp])
7 ✔
3159
                            ) {
3160
                                $returnTmp->add($dataTmp[$keyTmp]);
×
3161

3162
                                continue;
×
3163
                            }
3164

3165
                            if (
3166
                                \is_object($dataTmp) === true
7 ✔
3167
                                &&
3168
                                \property_exists($dataTmp, $keyTmp)
7 ✔
3169
                            ) {
3170
                                $returnTmp->add($dataTmp->{$keyTmp});
7 ✔
3171

3172
                                continue;
7 ✔
3173
                            }
3174
                        }
3175

3176
                        if ($returnTmp->count() > 0) {
7 ✔
3177
                            return $returnTmp;
7 ✔
3178
                        }
3179
                    }
3180

3181
                    return $fallback instanceof \Closure ? $fallback() : $fallback;
84 ✔
3182
                }
3183
            }
3184
        }
3185

3186
        if (isset($usedArrayTmp)) {
406 ✔
3187
            if (!$usePath && !isset($usedArrayTmp[$key])) {
196 ✔
3188
                return $fallback instanceof \Closure ? $fallback() : $fallback;
×
3189
            }
3190

3191
            if (\is_array($usedArrayTmp)) {
196 ✔
3192
                return static::create(
42 ✔
3193
                    [],
42 ✔
3194
                    $this->iteratorClass,
42 ✔
3195
                    false
42 ✔
3196
                )->createByReference($usedArrayTmp);
42 ✔
3197
            }
3198

3199
            return $usedArrayTmp;
196 ✔
3200
        }
3201

3202
        if (!$usePath && !isset($usedArray[$key])) {
210 ✔
3203
            return $fallback instanceof \Closure ? $fallback() : $fallback;
210 ✔
3204
        }
3205

3206
        return static::create(
×
3207
            [],
×
3208
            $this->iteratorClass,
×
3209
            false
×
3210
        )->createByReference($usedArray);
×
3211
    }
3212

3213
    /**
3214
     * alias: for "Arrayy->toArray()"
3215
     *
3216
     * @return array
3217
     *
3218
     * @see          Arrayy::getArray()
3219
     *
3220
     * @phpstan-return array<TKey,T>
3221
     */
3222
    public function getAll(): array
3223
    {
3224
        /** @var array<TKey,T> $return */
3225
        $return = $this->toArray();
105 ✔
3226

3227
        return $return;
105 ✔
3228
    }
3229

3230
    /**
3231
     * Get the current array from the "Arrayy"-object.
3232
     *
3233
     * alias for "toArray()"
3234
     *
3235
     * @param bool $convertAllArrayyElements <p>
3236
     *                                       Convert all Child-"Arrayy" objects also to arrays.
3237
     *                                       </p>
3238
     * @param bool $preserveKeys             <p>
3239
     *                                       e.g.: A generator maybe return the same key more than once,
3240
     *                                       so maybe you will ignore the keys.
3241
     *                                       </p>
3242
     *
3243
     * @return array
3244
     *
3245
     * @phpstan-return array<array-key,T>|array<TKey,T>
3246
     * @psalm-mutation-free
3247
     *
3248
     * @see Arrayy::toArray()
3249
     */
3250
    public function getArray(
3251
        bool $convertAllArrayyElements = false,
3252
        bool $preserveKeys = true
3253
    ): array {
3254
        return $this->toArray(
3,640 ✔
3255
            $convertAllArrayyElements,
3,640 ✔
3256
            $preserveKeys
3,640 ✔
3257
        );
3,640 ✔
3258
    }
3259

3260
    /**
3261
     * Create an instance from JSON using the built-in mapper.
3262
     *
3263
     * For Arrayy models with property checks enabled, phpdoc array-shape annotations,
3264
     * legacy `@property` definitions, and native declared properties are used for metadata and type checks.
3265
     * Add a property-level `@var` annotation if a native `array` property also needs
3266
     * element-type validation.
3267
     *
3268
     * @param string $json
3269
     *
3270
     * @return static
3271
     *                <p>(Immutable)</p>
3272
     */
3273
    public static function createFromJsonMapper(string $json)
3274
    {
3275
        // init
3276
        $class = static::create();
56 ✔
3277

3278
        $jsonObject = \json_decode($json, false);
56 ✔
3279

3280
        $mapper = new \Arrayy\Mapper\Json();
56 ✔
3281
        $mapper->undefinedPropertyHandler = static function ($object, $key, $jsonValue) use ($class) {
56 ✔
3282
            if ($class->checkPropertiesMismatchInConstructor) {
×
3283
                throw new \TypeError('Property mismatch - input: ' . \print_r(['key' => $key, 'jsonValue' => $jsonValue], true) . ' for object: ' . \get_class($object));
×
3284
            }
3285
        };
56 ✔
3286

3287
        /** @var static $return - hack for phpstan */
3288
        $return = $mapper->map($jsonObject, $class);
56 ✔
3289

3290
        return $return;
35 ✔
3291
    }
3292

3293
    /**
3294
     * @return array<array-key,TypeCheckInterface>|TypeCheckArray<array-key,TypeCheckInterface>
3295
     *
3296
     * @internal
3297
     */
3298
    public function getPhpDocPropertiesFromClass()
3299
    {
3300
        if ($this->properties === []) {
152 ✔
3301
            $this->properties = $this->getPropertiesFromPhpDoc();
82 ✔
3302
        }
3303

3304
        return $this->properties;
152 ✔
3305
    }
3306

3307
    /**
3308
     * Get the current array from the "Arrayy"-object as list.
3309
     *
3310
     * alias for "toList()"
3311
     *
3312
     * @param bool $convertAllArrayyElements <p>
3313
     *                                       Convert all Child-"Arrayy" objects also to arrays.
3314
     *                                       </p>
3315
     *
3316
     * @return array
3317
     *
3318
     * @phpstan-return list<T>
3319
     * @psalm-mutation-free
3320
     *
3321
     * @see Arrayy::toList()
3322
     */
3323
    public function getList(bool $convertAllArrayyElements = false): array
3324
    {
3325
        return $this->toList($convertAllArrayyElements);
7 ✔
3326
    }
3327

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

3364
                    if ($indexKey !== null) {
7 ✔
3365
                        foreach ($value as $keyInner => $valueInner) {
7 ✔
3366
                            if ($indexKey === $keyInner) {
7 ✔
3367
                                $newKey = $valueInner;
7 ✔
3368
                            }
3369

3370
                            if ($columnKey === $keyInner) {
7 ✔
3371
                                $newValue = $valueInner;
7 ✔
3372
                                $newValueFound = true;
7 ✔
3373
                            }
3374
                        }
3375
                    } else {
3376
                        foreach ($value as $keyInner => $valueInner) {
7 ✔
3377
                            if ($columnKey === $keyInner) {
7 ✔
3378
                                $newValue = $valueInner;
7 ✔
3379
                                $newValueFound = true;
7 ✔
3380
                            }
3381
                        }
3382
                    }
3383

3384
                    if ($newValueFound === false) {
7 ✔
3385
                        if ($newKey !== null) {
7 ✔
3386
                            yield $newKey => $value;
7 ✔
3387
                        } else {
3388
                            yield $value;
7 ✔
3389
                        }
3390
                    } else {
3391
                        /** @noinspection NestedPositiveIfStatementsInspection */
3392
                        if ($newKey !== null) {
7 ✔
3393
                            yield $newKey => $newValue;
7 ✔
3394
                        } else {
3395
                            yield $newValue;
7 ✔
3396
                        }
3397
                    }
3398
                }
3399
            };
7 ✔
3400
        }
3401

3402
        return static::create(
7 ✔
3403
            $generator,
7 ✔
3404
            $this->iteratorClass,
7 ✔
3405
            false
7 ✔
3406
        );
7 ✔
3407
    }
3408

3409
    /**
3410
     * Get the current array from the "Arrayy"-object as generator by reference.
3411
     *
3412
     * @return \Generator
3413
     *
3414
     * @phpstan-return \Generator<mixed,T>|\Generator<TKey,T>
3415
     */
3416
    public function &getGeneratorByReference(): \Generator
3417
    {
3418
        if ($this->generator instanceof ArrayyRewindableGenerator) {
602 ✔
3419
            foreach ($this->generator as $key => $value) {
119 ✔
3420
                yield $key => $value;
119 ✔
3421
            }
3422

3423
            return;
35 ✔
3424
        }
3425

3426
        // -> false-positive -> see "&$value"
3427
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
3428
        foreach ($this->array as $key => &$value) {
490 ✔
3429
            yield $key => $value;
441 ✔
3430
        }
3431
    }
3432

3433
    /**
3434
     * Get the current array from the "Arrayy"-object as generator.
3435
     *
3436
     * @return \Generator
3437
     *
3438
     * @phpstan-return \Generator<mixed,T>|\Generator<TKey,T>
3439
     * @psalm-mutation-free
3440
     */
3441
    public function getGenerator(): \Generator
3442
    {
3443
        if ($this->generator instanceof ArrayyRewindableGenerator) {
7,873 ✔
3444
            yield from $this->generator;
553 ✔
3445

3446
            return;
553 ✔
3447
        }
3448

3449
        yield from $this->array;
7,859 ✔
3450
    }
3451

3452
    /**
3453
     * Get the current array from the "Arrayy"-object as generator.
3454
     *
3455
     * @return \Generator
3456
     *
3457
     * @phpstan-return \Generator<mixed,T>|\Generator<TKey,T>
3458
     * @psalm-mutation-free
3459
     */
3460
    public function getBackwardsGenerator(): \Generator
3461
    {
3462
        yield from $this->reverseKeepIndex();
28 ✔
3463
    }
3464

3465
    /**
3466
     * alias: for "Arrayy->keys()"
3467
     *
3468
     * @return static
3469
     *                <p>(Immutable)</p>
3470
     *
3471
     * @see          Arrayy::keys()
3472
     *
3473
     * @phpstan-return static
3474
     * @psalm-mutation-free
3475
     */
3476
    public function getKeys()
3477
    {
3478
        return $this->keys();
14 ✔
3479
    }
3480

3481
    /**
3482
     * Get the current array from the "Arrayy"-object as object.
3483
     *
3484
     * @return \stdClass
3485
     */
3486
    public function getObject(): \stdClass
3487
    {
3488
        return self::arrayToObject($this->toArray());
28 ✔
3489
    }
3490

3491
    /**
3492
     * alias: for "Arrayy->randomImmutable()"
3493
     *
3494
     * @return static
3495
     *                <p>(Immutable)</p>
3496
     *
3497
     * @see          Arrayy::randomImmutable()
3498
     *
3499
     * @phpstan-return static
3500
     */
3501
    public function getRandom(): self
3502
    {
3503
        return $this->randomImmutable();
28 ✔
3504
    }
3505

3506
    /**
3507
     * alias: for "Arrayy->randomKey()"
3508
     *
3509
     * @return mixed|null
3510
     *                    <p>Get a key/index or null if there wasn't a key/index.</p>
3511
     *
3512
     * @phpstan-return null|TKey
3513
     *
3514
     * @see Arrayy::randomKey()
3515
     */
3516
    public function getRandomKey()
3517
    {
3518
        return $this->randomKey();
21 ✔
3519
    }
3520

3521
    /**
3522
     * alias: for "Arrayy->randomKeys()"
3523
     *
3524
     * @param int $number
3525
     *
3526
     * @return static
3527
     *                <p>(Immutable)</p>
3528
     *
3529
     * @see          Arrayy::randomKeys()
3530
     *
3531
     * @phpstan-return static
3532
     */
3533
    public function getRandomKeys(int $number): self
3534
    {
3535
        return $this->randomKeys($number);
56 ✔
3536
    }
3537

3538
    /**
3539
     * alias: for "Arrayy->randomValue()"
3540
     *
3541
     * @return mixed|null
3542
     *                    <p>Get a random value or null if there wasn't a value.</p>
3543
     *
3544
     * @phpstan-return null|T
3545
     *
3546
     * @see Arrayy::randomValue()
3547
     */
3548
    public function getRandomValue()
3549
    {
3550
        return $this->randomValue();
21 ✔
3551
    }
3552

3553
    /**
3554
     * alias: for "Arrayy->randomValues()"
3555
     *
3556
     * @param int $number
3557
     *
3558
     * @return static
3559
     *                <p>(Immutable)</p>
3560
     *
3561
     * @see          Arrayy::randomValues()
3562
     *
3563
     * @phpstan-return static
3564
     */
3565
    public function getRandomValues(int $number): self
3566
    {
3567
        return $this->randomValues($number);
42 ✔
3568
    }
3569

3570
    /**
3571
     * Gets all values.
3572
     *
3573
     * @return static
3574
     *                <p>The values of all elements in this array, in the order they
3575
     *                appear in the array.</p>
3576
     *
3577
     * @phpstan-return static
3578
     */
3579
    public function getValues()
3580
    {
3581
        $this->generatorToArray(false);
28 ✔
3582

3583
        return static::create(
28 ✔
3584
            \array_values($this->array),
28 ✔
3585
            $this->iteratorClass,
28 ✔
3586
            false
28 ✔
3587
        );
28 ✔
3588
    }
3589

3590
    /**
3591
     * Gets all values via Generator.
3592
     *
3593
     * @return \Generator
3594
     *                    <p>The values of all elements in this array, in the order they
3595
     *                    appear in the array as Generator.</p>
3596
     *
3597
     * @phpstan-return \Generator<TKey,T>
3598
     */
3599
    public function getValuesYield(): \Generator
3600
    {
3601
        yield from $this->getGenerator();
28 ✔
3602
    }
3603

3604
    /**
3605
     * Group values from a array according to the results of a closure.
3606
     *
3607
     * @param callable|int|string $grouper  <p>A callable function name.</p>
3608
     * @param bool                $saveKeys
3609
     *
3610
     * @return static
3611
     *                <p>(Immutable)</p>
3612
     *
3613
     * @phpstan-param \Closure(T,TKey):TKey|TKey $grouper
3614
     * @phpstan-return static
3615
     * @psalm-mutation-free
3616
     */
3617
    public function group($grouper, bool $saveKeys = false): self
3618
    {
3619
        // init
3620
        $result = [];
28 ✔
3621

3622
        // Iterate over values, group by property/results from closure.
3623
        foreach ($this->getGenerator() as $key => $value) {
28 ✔
3624
            if (\is_callable($grouper) === true) {
28 ✔
3625
                $groupKey = $grouper($value, $key);
21 ✔
3626
            } else {
3627
                $groupKey = $this->get($grouper);
7 ✔
3628
            }
3629

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

3632
            if ($groupKey instanceof self) {
28 ✔
3633
                $groupKey = $groupKey->toArray();
×
3634
            }
3635

3636
            if ($newValue instanceof self) {
28 ✔
3637
                $newValue = $newValue->toArray();
28 ✔
3638
            }
3639

3640
            // Add to result.
3641
            if ($groupKey !== null) {
28 ✔
3642
                $result[$groupKey] = $newValue;
21 ✔
3643

3644
                if ($saveKeys) {
21 ✔
3645
                    $result[$groupKey][$key] = $value;
14 ✔
3646
                } else {
3647
                    $result[$groupKey][] = $value;
7 ✔
3648
                }
3649
            }
3650
        }
3651

3652
        return static::create(
28 ✔
3653
            $result,
28 ✔
3654
            $this->iteratorClass,
28 ✔
3655
            false
28 ✔
3656
        );
28 ✔
3657
    }
3658

3659
    /**
3660
     * Check if an array has a given key.
3661
     *
3662
     * @param mixed $key
3663
     *
3664
     * @return bool
3665
     *
3666
     * @phpstan-param null|TKey|TKey[] $key
3667
     */
3668
    public function has($key): bool
3669
    {
3670
        static $UN_FOUND = null;
210 ✔
3671

3672
        if ($UN_FOUND === null) {
210 ✔
3673
            // Generate unique string to use as marker.
3674
            $UN_FOUND = 'arrayy--' . \uniqid('arrayy', true);
7 ✔
3675
        }
3676

3677
        if (\is_array($key)) {
210 ✔
3678
            if ($key === []) {
7 ✔
3679
                return false;
×
3680
            }
3681

3682
            foreach ($key as $keyTmp) {
7 ✔
3683
                $found = ($this->get($keyTmp, $UN_FOUND) !== $UN_FOUND);
7 ✔
3684
                if ($found === false) {
7 ✔
3685
                    return false;
7 ✔
3686
                }
3687
            }
3688

3689
            return true;
7 ✔
3690
        }
3691

3692
        return $this->get($key, $UN_FOUND) !== $UN_FOUND;
203 ✔
3693
    }
3694

3695
    /**
3696
     * Check if an array has a given value.
3697
     *
3698
     * INFO: If you need to search recursive please use ```contains($value, true)```.
3699
     *
3700
     * @param mixed $value
3701
     *
3702
     * @return bool
3703
     *
3704
     * @phpstan-param T $value
3705
     */
3706
    public function hasValue($value): bool
3707
    {
3708
        return $this->contains($value);
7 ✔
3709
    }
3710

3711
    /**
3712
     * Implodes the values of this array.
3713
     *
3714
     * EXAMPLE: <code>
3715
     * a([0 => -9, 1, 2])->implode('|'); // '-9|1|2'
3716
     * </code>
3717
     *
3718
     * @param string $glue
3719
     * @param string $prefix
3720
     *
3721
     * @return string
3722
     * @psalm-mutation-free
3723
     */
3724
    public function implode(string $glue = '', string $prefix = ''): string
3725
    {
3726
        return $prefix . $this->implode_recursive($glue, $this->toArray(), false);
203 ✔
3727
    }
3728

3729
    /**
3730
     * Implodes the keys of this array.
3731
     *
3732
     * @param string $glue
3733
     *
3734
     * @return string
3735
     * @psalm-mutation-free
3736
     */
3737
    public function implodeKeys(string $glue = ''): string
3738
    {
3739
        return $this->implode_recursive($glue, $this->toArray(), true);
56 ✔
3740
    }
3741

3742
    /**
3743
     * Given a list and an iterate-function that returns
3744
     * a key for each element in the list (or a property name),
3745
     * returns an object with an index of each item.
3746
     *
3747
     * @param int|string $key
3748
     *
3749
     * @return static
3750
     *                <p>(Immutable)</p>
3751
     *
3752
     * @phpstan-param array-key $key
3753
     * @phpstan-return static
3754
     * @psalm-mutation-free
3755
     */
3756
    public function indexBy($key): self
3757
    {
3758
        // init
3759
        $results = [];
28 ✔
3760

3761
        foreach ($this->getGenerator() as $a) {
28 ✔
3762
            if (\array_key_exists($key, $a) === true) {
28 ✔
3763
                $results[$a[$key]] = $a;
21 ✔
3764
            }
3765
        }
3766

3767
        return static::create(
28 ✔
3768
            $results,
28 ✔
3769
            $this->iteratorClass,
28 ✔
3770
            false
28 ✔
3771
        );
28 ✔
3772
    }
3773

3774
    /**
3775
     * alias: for "Arrayy->searchIndex()"
3776
     *
3777
     * @param mixed $value
3778
     *                     <p>The value to search for.</p>
3779
     *
3780
     * @return false|int|string
3781
     *
3782
     * @phpstan-param T $value
3783
     * @phpstan-return false|TKey
3784
     *
3785
     * @see Arrayy::searchIndex()
3786
     */
3787
    public function indexOf($value)
3788
    {
3789
        return $this->searchIndex($value);
28 ✔
3790
    }
3791

3792
    /**
3793
     * Get everything but the last..$to items.
3794
     *
3795
     * EXAMPLE: <code>
3796
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->initial(2); // Arrayy[0 => 'foo']
3797
     * </code>
3798
     *
3799
     * @param int $to
3800
     *
3801
     * @return static
3802
     *                <p>(Immutable)</p>
3803
     *
3804
     * @phpstan-return static
3805
     * @psalm-mutation-free
3806
     */
3807
    public function initial(int $to = 1): self
3808
    {
3809
        return $this->firstsImmutable(\count($this->toArray(), \COUNT_NORMAL) - $to);
84 ✔
3810
    }
3811

3812
    /**
3813
     * Return an array with all elements found in input array.
3814
     *
3815
     * EXAMPLE: <code>
3816
     * a(['foo', 'bar'])->intersection(['bar', 'baz']); // Arrayy['bar']
3817
     * </code>
3818
     *
3819
     * @param array $search
3820
     * @param bool  $keepKeys
3821
     *
3822
     * @return static
3823
     *                <p>(Immutable)</p>
3824
     *
3825
     * @phpstan-param  array<TKey,T> $search
3826
     * @phpstan-return static
3827
     * @psalm-mutation-free
3828
     */
3829
    public function intersection(array $search, bool $keepKeys = false): self
3830
    {
3831
        if ($keepKeys) {
28 ✔
3832
            /**
3833
             * @psalm-suppress MissingClosureReturnType
3834
             * @psalm-suppress MissingClosureParamType
3835
             */
3836
            return static::create(
7 ✔
3837
                \array_uintersect(
7 ✔
3838
                    $this->toArray(),
7 ✔
3839
                    $search,
7 ✔
3840
                    static function ($a, $b) {
7 ✔
3841
                        return $a === $b ? 0 : -1;
7 ✔
3842
                    }
7 ✔
3843
                ),
7 ✔
3844
                $this->iteratorClass,
7 ✔
3845
                false
7 ✔
3846
            );
7 ✔
3847
        }
3848

3849
        return static::create(
21 ✔
3850
            \array_values(\array_intersect($this->toArray(), $search)),
21 ✔
3851
            $this->iteratorClass,
21 ✔
3852
            false
21 ✔
3853
        );
21 ✔
3854
    }
3855

3856
    /**
3857
     * Return an array with all elements found in input array.
3858
     *
3859
     * @param array ...$array
3860
     *
3861
     * @return static
3862
     *                <p>(Immutable)</p>
3863
     *
3864
     * @phpstan-param  array<array<TKey,T>> ...$array
3865
     * @phpstan-return static
3866
     * @psalm-mutation-free
3867
     */
3868
    public function intersectionMulti(...$array): self
3869
    {
3870
        return static::create(
7 ✔
3871
            \array_values(\array_intersect($this->toArray(), ...$array)),
7 ✔
3872
            $this->iteratorClass,
7 ✔
3873
            false
7 ✔
3874
        );
7 ✔
3875
    }
3876

3877
    /**
3878
     * Return a boolean flag which indicates whether the two input arrays have any common elements.
3879
     *
3880
     * EXAMPLE: <code>
3881
     * a(['foo', 'bar'])->intersects(['fΓΆΓΆ', 'bΓ€r']); // false
3882
     * </code>
3883
     *
3884
     * @param array $search
3885
     *
3886
     * @return bool
3887
     *
3888
     * @phpstan-param array<TKey,T> $search
3889
     */
3890
    public function intersects(array $search): bool
3891
    {
3892
        return $this->intersection($search)->count() > 0;
7 ✔
3893
    }
3894

3895
    /**
3896
     * Invoke a function on all of an array's values.
3897
     *
3898
     * @param callable $callable
3899
     * @param mixed    $arguments
3900
     *
3901
     * @return static
3902
     *                <p>(Immutable)</p>
3903
     *
3904
     * @phpstan-param  callable(T,mixed=):mixed $callable
3905
     * @phpstan-return static|static
3906
     * @psalm-mutation-free
3907
     */
3908
    public function invoke($callable, $arguments = []): self
3909
    {
3910
        // If one argument given for each iteration, create an array for it.
3911
        if (!\is_array($arguments)) {
7 ✔
3912
            $arguments = \array_fill(
7 ✔
3913
                0,
7 ✔
3914
                $this->count(),
7 ✔
3915
                $arguments
7 ✔
3916
            );
7 ✔
3917
        }
3918

3919
        // If the callable has arguments, pass them.
3920
        if ($arguments) {
7 ✔
3921
            $array = \array_map($callable, $this->toArray(), $arguments);
7 ✔
3922
        } else {
3923
            $array = $this->map($callable);
7 ✔
3924
        }
3925

3926
        return static::create(
7 ✔
3927
            $array,
7 ✔
3928
            $this->iteratorClass,
7 ✔
3929
            false
7 ✔
3930
        );
7 ✔
3931
    }
3932

3933
    /**
3934
     * Check whether array is associative or not.
3935
     *
3936
     * EXAMPLE: <code>
3937
     * a(['foo' => 'bar', 2, 3])->isAssoc(); // true
3938
     * </code>
3939
     *
3940
     * @param bool $recursive
3941
     *
3942
     * @return bool
3943
     *              <p>Returns true if associative, false otherwise.</p>
3944
     */
3945
    public function isAssoc(bool $recursive = false): bool
3946
    {
3947
        if ($this->isEmpty()) {
105 ✔
3948
            return false;
21 ✔
3949
        }
3950

3951
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
3952
        foreach ($this->keys($recursive)->getGeneratorByReference() as &$key) {
91 ✔
3953
            if ((string) $key !== $key) {
91 ✔
3954
                return false;
77 ✔
3955
            }
3956
        }
3957

3958
        return true;
21 ✔
3959
    }
3960

3961
    /**
3962
     * Check if a given key or keys are empty.
3963
     *
3964
     * @param int|int[]|string|string[]|null $keys
3965
     *
3966
     * @return bool
3967
     *              <p>Returns true if empty, false otherwise.</p>
3968
     * @psalm-mutation-free
3969
     */
3970
    public function isEmpty($keys = null): bool
3971
    {
3972
        if ($this->generator) {
315 ✔
3973
            return $this->toArray() === [];
×
3974
        }
3975

3976
        if ($keys === null) {
315 ✔
3977
            return $this->array === [];
301 ✔
3978
        }
3979

3980
        foreach ((array) $keys as $key) {
14 ✔
3981
            if (!empty($this->get($key))) {
14 ✔
3982
                return false;
14 ✔
3983
            }
3984
        }
3985

3986
        return true;
14 ✔
3987
    }
3988

3989
    /**
3990
     * Check if the current array is equal to the given "$array" or not.
3991
     *
3992
     * EXAMPLE: <code>
3993
     * a(['πŸ’©'])->isEqual(['πŸ’©']); // true
3994
     * </code>
3995
     *
3996
     * @param array $array
3997
     *
3998
     * @return bool
3999
     *
4000
     * @phpstan-param array<TKey,T> $array
4001
     */
4002
    public function isEqual(array $array): bool
4003
    {
4004
        return $this->toArray() === $array;
7 ✔
4005
    }
4006

4007
    /**
4008
     * Check if the current array is a multi-array.
4009
     *
4010
     * EXAMPLE: <code>
4011
     * a(['foo' => [1, 2 , 3]])->isMultiArray(); // true
4012
     * </code>
4013
     *
4014
     * @return bool
4015
     */
4016
    public function isMultiArray(): bool
4017
    {
4018
        foreach ($this->getGenerator() as $value) {
154 ✔
4019
            if (\is_array($value)) {
140 ✔
4020
                return true;
35 ✔
4021
            }
4022
        }
4023

4024
        return false;
126 ✔
4025
    }
4026

4027
    /**
4028
     * Check whether array is numeric or not.
4029
     *
4030
     * @return bool
4031
     *              <p>Returns true if numeric, false otherwise.</p>
4032
     */
4033
    public function isNumeric(): bool
4034
    {
4035
        if ($this->isEmpty()) {
35 ✔
4036
            return false;
14 ✔
4037
        }
4038

4039
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4040
        foreach ($this->keys()->getGeneratorByReference() as &$key) {
28 ✔
4041
            if ((int) $key !== $key) {
28 ✔
4042
                return false;
14 ✔
4043
            }
4044
        }
4045

4046
        return true;
14 ✔
4047
    }
4048

4049
    /**
4050
     * Check if the current array is sequential [0, 1, 2, 3, 4, 5 ...] or not.
4051
     *
4052
     * EXAMPLE: <code>
4053
     * a([0 => 'foo', 1 => 'lall', 2 => 'foobar'])->isSequential(); // true
4054
     * </code>
4055
     *
4056
     * INFO: If the array is empty we count it as non-sequential.
4057
     *
4058
     * @param bool $recursive
4059
     *
4060
     * @return bool
4061
     * @psalm-mutation-free
4062
     */
4063
    public function isSequential(bool $recursive = false): bool
4064
    {
4065
        $i = 0;
70 ✔
4066
        foreach ($this->getGenerator() as $key => $value) {
70 ✔
4067
            if (
4068
                $recursive
63 ✔
4069
                &&
4070
                (\is_array($value) || $value instanceof \Traversable)
63 ✔
4071
                &&
4072
                self::create($value)->isSequential() === false
63 ✔
4073
            ) {
4074
                return false;
7 ✔
4075
            }
4076

4077
            if ($key !== $i) {
63 ✔
4078
                return false;
21 ✔
4079
            }
4080

4081
            ++$i;
56 ✔
4082
        }
4083

4084
        return !($i === 0);
63 ✔
4085
    }
4086

4087
    /**
4088
     * @return array
4089
     *
4090
     * @phpstan-return array<TKey,T>
4091
     */
4092
    public function jsonSerialize(): array
4093
    {
4094
        /** @var array<TKey,T> $return */
4095
        $return = $this->toArray();
14 ✔
4096

4097
        return $return;
14 ✔
4098
    }
4099

4100
    /**
4101
     * Gets the key/index of the element at the current internal iterator position.
4102
     *
4103
     * @return int|string|null
4104
     * @phpstan-return array-key|null
4105
     */
4106
    public function key()
4107
    {
4108
        if ($this->generator) {
×
4109
            return $this->generator->key();
×
4110
        }
4111

4112
        return \key($this->array);
×
4113
    }
4114

4115
    /**
4116
     * Checks if the given key exists in the provided array.
4117
     *
4118
     * INFO: This method only use "array_key_exists()" if you want to use "dot"-notation,
4119
     *       then you need to use "Arrayy->offsetExists()".
4120
     *
4121
     * @param int|string $key the key to look for
4122
     *
4123
     * @return bool
4124
     * @psalm-mutation-free
4125
     */
4126
    public function keyExists($key): bool
4127
    {
4128
        foreach ($this->getGenerator() as $keyTmp => $value) {
1,447 ✔
4129
            if ($key === $keyTmp) {
1,412 ✔
4130
                return true;
1,307 ✔
4131
            }
4132
        }
4133

4134
        return false;
1,022 ✔
4135
    }
4136

4137
    /**
4138
     * Get all keys from the current array.
4139
     *
4140
     * EXAMPLE: <code>
4141
     * a([1 => 'foo', 2 => 'foo2', 3 => 'bar'])->keys(); // Arrayy[1, 2, 3]
4142
     * </code>
4143
     *
4144
     * @param bool       $recursive
4145
     *                                  [optional] <p>
4146
     *                                  Get all keys, also from all sub-arrays from an multi-dimensional array.
4147
     *                                  </p>
4148
     * @param mixed|null $search_values
4149
     *                                  [optional] <p>
4150
     *                                  If specified, then only keys containing these values are returned.
4151
     *                                  </p>
4152
     * @param bool       $strict
4153
     *                                  [optional] <p>
4154
     *                                  Determines if strict comparison (===) should be used during the search.
4155
     *                                  </p>
4156
     *
4157
     * @return static
4158
     *                <p>(Immutable) An array of all the keys in input.</p>
4159
     *
4160
     * @phpstan-param null|T|T[] $search_values
4161
     * @phpstan-return static
4162
     *
4163
     * @psalm-mutation-free
4164
     */
4165
    public function keys(
4166
        bool $recursive = false,
4167
        $search_values = null,
4168
        bool $strict = true
4169
    ): self {
4170
        // recursive
4171

4172
        if ($recursive === true) {
210 ✔
4173
            $array = $this->array_keys_recursive(
28 ✔
4174
                null,
28 ✔
4175
                $search_values,
28 ✔
4176
                $strict
28 ✔
4177
            );
28 ✔
4178

4179
            return static::create(
28 ✔
4180
                $array,
28 ✔
4181
                $this->iteratorClass,
28 ✔
4182
                false
28 ✔
4183
            );
28 ✔
4184
        }
4185

4186
        // non recursive
4187

4188
        if ($search_values === null) {
203 ✔
4189
            $arrayFunction = function (): \Generator {
203 ✔
4190
                foreach ($this->getGenerator() as $key => $value) {
203 ✔
4191
                    yield $key;
189 ✔
4192
                }
4193
            };
203 ✔
4194
        } else {
4195
            $arrayFunction = function () use ($search_values, $strict): \Generator {
7 ✔
4196
                $is_array_tmp = \is_array($search_values);
7 ✔
4197

4198
                /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4199
                foreach ($this->getGeneratorByReference() as $key => &$value) {
7 ✔
4200
                    if (
4201
                        (
4202
                            $is_array_tmp === false
7 ✔
4203
                            &&
7 ✔
4204
                            $strict === true
7 ✔
4205
                            &&
7 ✔
4206
                            $search_values === $value
7 ✔
4207
                        )
4208
                        ||
4209
                        (
4210
                            $is_array_tmp === false
7 ✔
4211
                            &&
7 ✔
4212
                            $strict === false
7 ✔
4213
                            &&
7 ✔
4214
                            $search_values == $value
7 ✔
4215
                        )
4216
                        ||
4217
                        (
4218
                            $is_array_tmp === true
7 ✔
4219
                            &&
7 ✔
4220
                            \in_array($value, $search_values, $strict)
7 ✔
4221
                        )
4222
                    ) {
4223
                        yield $key;
7 ✔
4224
                    }
4225
                }
4226
            };
7 ✔
4227
        }
4228

4229
        return static::create(
203 ✔
4230
            $arrayFunction,
203 ✔
4231
            $this->iteratorClass,
203 ✔
4232
            false
203 ✔
4233
        );
203 ✔
4234
    }
4235

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

4254
        \krsort($this->array, $sort_flags);
28 ✔
4255

4256
        return $this;
28 ✔
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>(Immutable)</p>
4270
     *
4271
     * @phpstan-return static
4272
     * @psalm-mutation-free
4273
     */
4274
    public function krsortImmutable(int $sort_flags = 0): self
4275
    {
4276
        $that = clone $this;
28 ✔
4277

4278
        /**
4279
         * @psalm-suppress ImpureMethodCall - object is already cloned
4280
         */
4281
        $that->krsort($sort_flags);
28 ✔
4282

4283
        return $that;
28 ✔
4284
    }
4285

4286
    /**
4287
     * Get the last value from the current array.
4288
     *
4289
     * EXAMPLE: <code>
4290
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->last(); // 'lall'
4291
     * </code>
4292
     *
4293
     * @return mixed|null
4294
     *                    <p>Return null if there wasn't a element.</p>
4295
     *
4296
     * @phpstan-return T|null
4297
     * @psalm-mutation-free
4298
     */
4299
    public function last()
4300
    {
4301
        $key_last = $this->lastKey();
119 ✔
4302
        if ($key_last === null) {
119 ✔
4303
            return null;
14 ✔
4304
        }
4305

4306
        /** @var T $value_last */
4307
        $value_last = $this->get($key_last);
105 ✔
4308

4309
        return $value_last;
105 ✔
4310
    }
4311

4312
    /**
4313
     * Get the last key from the current array.
4314
     *
4315
     * @return mixed|null
4316
     *                    <p>Return null if there wasn't a element.</p>
4317
     *
4318
     * @phpstan-return null|TKey
4319
     * @psalm-mutation-free
4320
     */
4321
    public function lastKey()
4322
    {
4323
        $this->generatorToArray();
147 ✔
4324

4325
        return \array_key_last($this->array);
147 ✔
4326
    }
4327

4328
    /**
4329
     * Get the last value(s) from the current array.
4330
     *
4331
     * EXAMPLE: <code>
4332
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->lasts(2); // Arrayy[0 => 'bar', 1 => 'lall']
4333
     * </code>
4334
     *
4335
     * @param int|null $number
4336
     *
4337
     * @return static
4338
     *                <p>(Immutable)</p>
4339
     *
4340
     * @phpstan-return static
4341
     * @psalm-mutation-free
4342
     */
4343
    public function lastsImmutable(?int $number = null): self
4344
    {
4345
        if ($this->isEmpty()) {
91 ✔
4346
            return static::create(
7 ✔
4347
                [],
7 ✔
4348
                $this->iteratorClass,
7 ✔
4349
                false
7 ✔
4350
            );
7 ✔
4351
        }
4352

4353
        if ($number === null) {
84 ✔
4354
            $poppedValue = $this->last();
56 ✔
4355

4356
            if ($poppedValue === null) {
56 ✔
4357
                $poppedValue = [$poppedValue];
7 ✔
4358
            } else {
4359
                $poppedValue = (array) $poppedValue;
49 ✔
4360
            }
4361

4362
            $arrayy = static::create(
56 ✔
4363
                $poppedValue,
56 ✔
4364
                $this->iteratorClass,
56 ✔
4365
                false
56 ✔
4366
            );
56 ✔
4367
        } else {
4368
            $arrayy = $this->rest(-$number);
28 ✔
4369
        }
4370

4371
        return $arrayy;
84 ✔
4372
    }
4373

4374
    /**
4375
     * Get the last value(s) from the current array.
4376
     *
4377
     * EXAMPLE: <code>
4378
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->lasts(2); // Arrayy[0 => 'bar', 1 => 'lall']
4379
     * </code>
4380
     *
4381
     * @param int|null $number
4382
     *
4383
     * @return $this
4384
     *               <p>(Mutable)</p>
4385
     *
4386
     * @phpstan-return static
4387
     */
4388
    public function lastsMutable(?int $number = null): self
4389
    {
4390
        if ($this->isEmpty()) {
91 ✔
4391
            return $this;
7 ✔
4392
        }
4393

4394
        $this->array = $this->lastsImmutable($number)->toArray();
84 ✔
4395
        $this->generator = null;
84 ✔
4396

4397
        return $this;
84 ✔
4398
    }
4399

4400
    /**
4401
     * Count the values from the current array.
4402
     *
4403
     * alias: for "Arrayy->count()"
4404
     *
4405
     * @param int $mode
4406
     *
4407
     * @return int
4408
     *
4409
     * @see Arrayy::count()
4410
     */
4411
    public function length(int $mode = \COUNT_NORMAL): int
4412
    {
4413
        return $this->count($mode);
140 ✔
4414
    }
4415

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

4448
        return static::create(
49 ✔
4449
            function () use ($useArguments, $callable, $useKeyAsSecondParameter, $arguments) {
49 ✔
4450
                foreach ($this->getGenerator() as $key => $value) {
49 ✔
4451
                    if ($useArguments) {
42 ✔
4452
                        if ($useKeyAsSecondParameter) {
21 ✔
4453
                            yield $key => $callable($value, $key, ...$arguments);
×
4454
                        } else {
4455
                            yield $key => $callable($value, ...$arguments);
21 ✔
4456
                        }
4457
                    } else {
4458
                        /** @noinspection NestedPositiveIfStatementsInspection */
4459
                        if ($useKeyAsSecondParameter) {
42 ✔
4460
                            yield $key => $callable($value, $key);
×
4461
                        } else {
4462
                            yield $key => $callable($value);
42 ✔
4463
                        }
4464
                    }
4465
                }
4466
            },
49 ✔
4467
            $this->iteratorClass,
49 ✔
4468
            false
49 ✔
4469
        );
49 ✔
4470
    }
4471

4472
    /**
4473
     * Check if all items in current array match a truth test.
4474
     *
4475
     * EXAMPLE: <code>
4476
     * $closure = function ($value, $key) {
4477
     *     return ($value % 2 === 0);
4478
     * };
4479
     * a([2, 4, 8])->matches($closure); // true
4480
     * </code>
4481
     *
4482
     * @param \Closure $closure
4483
     *
4484
     * @return bool
4485
     *
4486
     * @phpstan-param \Closure(T,TKey):bool $closure
4487
     */
4488
    public function matches(\Closure $closure): bool
4489
    {
4490
        if ($this->count() === 0) {
105 ✔
4491
            return false;
14 ✔
4492
        }
4493

4494
        foreach ($this->getGenerator() as $key => $value) {
91 ✔
4495
            $value = $closure($value, $key);
91 ✔
4496

4497
            if ($value === false) {
91 ✔
4498
                return false;
49 ✔
4499
            }
4500
        }
4501

4502
        return true;
49 ✔
4503
    }
4504

4505
    /**
4506
     * Check if any item in the current array matches a truth test.
4507
     *
4508
     * EXAMPLE: <code>
4509
     * $closure = function ($value, $key) {
4510
     *     return ($value % 2 === 0);
4511
     * };
4512
     * a([1, 4, 7])->matches($closure); // true
4513
     * </code>
4514
     *
4515
     * @param \Closure $closure
4516
     *
4517
     * @return bool
4518
     *
4519
     * @phpstan-param \Closure(T,TKey):bool $closure
4520
     */
4521
    public function matchesAny(\Closure $closure): bool
4522
    {
4523
        if ($this->count() === 0) {
98 ✔
4524
            return false;
14 ✔
4525
        }
4526

4527
        foreach ($this->getGenerator() as $key => $value) {
84 ✔
4528
            $value = $closure($value, $key);
84 ✔
4529

4530
            if ($value === true) {
84 ✔
4531
                return true;
63 ✔
4532
            }
4533
        }
4534

4535
        return false;
28 ✔
4536
    }
4537

4538
    /**
4539
     * Get the max value from an array.
4540
     *
4541
     * EXAMPLE: <code>
4542
     * a([-9, -8, -7, 1.32])->max(); // 1.32
4543
     * </code>
4544
     *
4545
     * @return false|float|int|string
4546
     *                                <p>Will return false if there are no values.</p>
4547
     */
4548
    public function max()
4549
    {
4550
        if ($this->count() === 0) {
77 ✔
4551
            return false;
7 ✔
4552
        }
4553

4554
        $max = false;
70 ✔
4555
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4556
        foreach ($this->getGeneratorByReference() as &$value) {
70 ✔
4557
            if (
4558
                $max === false
70 ✔
4559
                ||
4560
                $value > $max
70 ✔
4561
            ) {
4562
                $max = $value;
70 ✔
4563
            }
4564
        }
4565

4566
        return $max;
70 ✔
4567
    }
4568

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

4603
        return static::create(
231 ✔
4604
            $result,
231 ✔
4605
            $this->iteratorClass,
231 ✔
4606
            false
231 ✔
4607
        );
231 ✔
4608
    }
4609

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

4645
        return static::create(
140 ✔
4646
            $result,
140 ✔
4647
            $this->iteratorClass,
140 ✔
4648
            false
140 ✔
4649
        );
140 ✔
4650
    }
4651

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

4686
        return static::create(
119 ✔
4687
            $result,
119 ✔
4688
            $this->iteratorClass,
119 ✔
4689
            false
119 ✔
4690
        );
119 ✔
4691
    }
4692

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

4728
        return static::create(
147 ✔
4729
            $result,
147 ✔
4730
            $this->iteratorClass,
147 ✔
4731
            false
147 ✔
4732
        );
147 ✔
4733
    }
4734

4735
    /**
4736
     * Return a meta object with property names from phpdoc array-shape annotations,
4737
     * `@property` tags, and native declared properties.
4738
     *
4739
     * @return ArrayyMeta|mixed|static
4740
     */
4741
    public static function meta()
4742
    {
4743
        return (new ArrayyMeta())->getMetaObject(static::class);
299 ✔
4744
    }
4745

4746
    /**
4747
     * Get the min value from an array.
4748
     *
4749
     * EXAMPLE: <code>
4750
     * a([-9, -8, -7, 1.32])->min(); // -9
4751
     * </code>
4752
     *
4753
     * @return false|mixed
4754
     *                     <p>Will return false if there are no values.</p>
4755
     *
4756
     * @phpstan-return false|T
4757
     */
4758
    public function min()
4759
    {
4760
        if ($this->count() === 0) {
77 ✔
4761
            return false;
7 ✔
4762
        }
4763

4764
        $min = false;
70 ✔
4765
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4766
        foreach ($this->getGeneratorByReference() as &$value) {
70 ✔
4767
            if (
4768
                $min === false
70 ✔
4769
                ||
4770
                $value < $min
70 ✔
4771
            ) {
4772
                $min = $value;
70 ✔
4773
            }
4774
        }
4775

4776
        return $min;
70 ✔
4777
    }
4778

4779
    /**
4780
     * Get the most used value from the array.
4781
     *
4782
     * @return mixed|null
4783
     *                    <p>(Immutable) Return null if there wasn't an element.</p>
4784
     *
4785
     * @phpstan-return T|null
4786
     * @psalm-mutation-free
4787
     */
4788
    public function mostUsedValue()
4789
    {
4790
        /* @phpstan-ignore-next-line | false-positive? maybe because we switch key-value via "countValues"? */
4791
        return $this->countValues()->arsortImmutable()->firstKey();
21 ✔
4792
    }
4793

4794
    /**
4795
     * Get the most used value from the array.
4796
     *
4797
     * @param int|null $number <p>How many values you will take?</p>
4798
     *
4799
     * @return static
4800
     *                <p>(Immutable)</p>
4801
     *
4802
     * @phpstan-return static
4803
     * @psalm-mutation-free
4804
     */
4805
    public function mostUsedValues(?int $number = null): self
4806
    {
4807
        return $this->countValues()->arsortImmutable()->firstsKeys($number);
21 ✔
4808
    }
4809

4810
    /**
4811
     * Move an array element to a new index.
4812
     *
4813
     * EXAMPLE: <code>
4814
     * $arr2 = new A(['A' => 'a', 'B' => 'b', 'C' => 'c', 'D' => 'd', 'E' => 'e']);
4815
     * $newArr2 = $arr2->moveElement('D', 1); // Arrayy['A' => 'a', 'D' => 'd', 'B' => 'b', 'C' => 'c', 'E' => 'e']
4816
     * </code>
4817
     *
4818
     * @param int|string $from
4819
     * @param int        $to
4820
     *
4821
     * @return static
4822
     *                <p>(Immutable)</p>
4823
     *
4824
     * @phpstan-return static
4825
     * @psalm-mutation-free
4826
     */
4827
    public function moveElement($from, $to): self
4828
    {
4829
        $array = $this->toArray();
7 ✔
4830

4831
        if ((int) $from === $from) {
7 ✔
4832
            $tmp = \array_splice($array, $from, 1);
7 ✔
4833
            \array_splice($array, (int) $to, 0, $tmp);
7 ✔
4834
            $output = $array;
7 ✔
4835
        } elseif ((string) $from === $from) {
7 ✔
4836
            $indexToMove = \array_search($from, \array_keys($array), true);
7 ✔
4837
            $itemToMove = $array[$from];
7 ✔
4838
            if ($indexToMove !== false) {
7 ✔
4839
                \array_splice($array, $indexToMove, 1);
7 ✔
4840
            }
4841
            $i = 0;
7 ✔
4842
            $output = [];
7 ✔
4843
            foreach ($array as $key => $item) {
7 ✔
4844
                if ($i === $to) {
7 ✔
4845
                    $output[$from] = $itemToMove;
7 ✔
4846
                }
4847
                $output[$key] = $item;
7 ✔
4848
                ++$i;
7 ✔
4849
            }
4850
        } else {
4851
            $output = [];
×
4852
        }
4853

4854
        return static::create(
7 ✔
4855
            $output,
7 ✔
4856
            $this->iteratorClass,
7 ✔
4857
            false
7 ✔
4858
        );
7 ✔
4859
    }
4860

4861
    /**
4862
     * Move an array element to the first place.
4863
     *
4864
     * INFO: Instead of "Arrayy->moveElement()" this method will NOT
4865
     *       loss the keys of an indexed array.
4866
     *
4867
     * @param int|string $key
4868
     *
4869
     * @return static
4870
     *                <p>(Immutable)</p>
4871
     *
4872
     * @phpstan-return static
4873
     * @psalm-mutation-free
4874
     */
4875
    public function moveElementToFirstPlace($key): self
4876
    {
4877
        $array = $this->toArray();
7 ✔
4878

4879
        if ($this->offsetExists($key)) {
7 ✔
4880
            $tmpValue = $this->get($key);
7 ✔
4881
            unset($array[$key]);
7 ✔
4882
            $array = [$key => $tmpValue] + $array;
7 ✔
4883
        }
4884

4885
        return static::create(
7 ✔
4886
            $array,
7 ✔
4887
            $this->iteratorClass,
7 ✔
4888
            false
7 ✔
4889
        );
7 ✔
4890
    }
4891

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

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

4916
        return static::create(
7 ✔
4917
            $array,
7 ✔
4918
            $this->iteratorClass,
7 ✔
4919
            false
7 ✔
4920
        );
7 ✔
4921
    }
4922

4923
    /**
4924
     * Moves the internal iterator position to the next element and returns this element.
4925
     *
4926
     * @return false|mixed
4927
     *                     <p>(Mutable) Will return false if there are no values.</p>
4928
     *
4929
     * @phpstan-return false|T
4930
     */
4931
    public function next()
4932
    {
4933
        if ($this->generator) {
×
4934
            $this->generator->next();
×
4935

4936
            return $this->generator->current() ?? false;
×
4937
        }
4938

4939
        return \next($this->array);
×
4940
    }
4941

4942
    /**
4943
     * Get the next nth keys and values from the array.
4944
     *
4945
     * @param int $step
4946
     * @param int $offset
4947
     *
4948
     * @return static
4949
     *                <p>(Immutable)</p>
4950
     *
4951
     * @phpstan-return static
4952
     * @psalm-mutation-free
4953
     */
4954
    public function nth(int $step, int $offset = 0): self
4955
    {
4956
        $arrayFunction = function () use ($step, $offset): \Generator {
7 ✔
4957
            $position = 0;
7 ✔
4958
            foreach ($this->getGenerator() as $key => $value) {
7 ✔
4959
                if ($position++ % $step !== $offset) {
7 ✔
4960
                    continue;
7 ✔
4961
                }
4962

4963
                yield $key => $value;
7 ✔
4964
            }
4965
        };
7 ✔
4966

4967
        return static::create(
7 ✔
4968
            $arrayFunction,
7 ✔
4969
            $this->iteratorClass,
7 ✔
4970
            false
7 ✔
4971
        );
7 ✔
4972
    }
4973

4974
    /**
4975
     * Get a subset of the items from the given array.
4976
     *
4977
     * @param int[]|string[] $keys
4978
     *
4979
     * @return static
4980
     *                <p>(Immutable)</p>
4981
     *
4982
     * @phpstan-param array-key[] $keys
4983
     * @phpstan-return static
4984
     * @psalm-mutation-free
4985
     */
4986
    public function only(array $keys): self
4987
    {
4988
        $keys = \array_flip($keys);
7 ✔
4989

4990
        $generator = function () use ($keys): \Generator {
7 ✔
4991
            foreach ($this->getGenerator() as $key => $value) {
7 ✔
4992
                if (isset($keys[$key])) {
7 ✔
4993
                    yield $key => $value;
7 ✔
4994
                }
4995
            }
4996
        };
7 ✔
4997

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

5005
    /**
5006
     * Pad array to the specified size with a given value.
5007
     *
5008
     * @param int   $size  <p>Size of the result array.</p>
5009
     * @param mixed $value <p>Empty value by default.</p>
5010
     *
5011
     * @return static
5012
     *                <p>(Immutable) Arrayy object padded to $size with $value.</p>
5013
     *
5014
     * @phpstan-return static
5015
     * @psalm-mutation-free
5016
     */
5017
    public function pad(int $size, $value): self
5018
    {
5019
        return static::create(
35 ✔
5020
            \array_pad($this->toArray(), $size, $value),
35 ✔
5021
            $this->iteratorClass,
35 ✔
5022
            false
35 ✔
5023
        );
35 ✔
5024
    }
5025

5026
    /**
5027
     * Partitions this array in two array according to a predicate.
5028
     * Keys are preserved in the resulting array.
5029
     *
5030
     * @param \Closure $closure
5031
     *                          <p>The predicate on which to partition.</p>
5032
     *
5033
     * @return array<int, static>
5034
     *                    <p>An array with two elements. The first element contains the array
5035
     *                    of elements where the predicate returned TRUE, the second element
5036
     *                    contains the array of elements where the predicate returned FALSE.</p>
5037
     *
5038
     * @phpstan-param \Closure(T,TKey):bool $closure
5039
     * @phpstan-return array<int, static>
5040
     */
5041
    public function partition(\Closure $closure): array
5042
    {
5043
        // init
5044
        $matches = [];
7 ✔
5045
        $noMatches = [];
7 ✔
5046

5047
        foreach ($this->getGenerator() as $key => $value) {
7 ✔
5048
            if ($closure($value, $key)) {
7 ✔
5049
                $matches[$key] = $value;
7 ✔
5050
            } else {
5051
                $noMatches[$key] = $value;
7 ✔
5052
            }
5053
        }
5054

5055
        return [self::create($matches), self::create($noMatches)];
7 ✔
5056
    }
5057

5058
    /**
5059
     * Pop a specified value off the end of the current array.
5060
     *
5061
     * @return mixed|null
5062
     *                    <p>(Mutable) The popped element from the current array or null if the array is e.g. empty.</p>
5063
     *
5064
     * @phpstan-return T|null
5065
     */
5066
    public function pop()
5067
    {
5068
        $this->generatorToArray();
35 ✔
5069

5070
        return \array_pop($this->array);
35 ✔
5071
    }
5072

5073
    /**
5074
     * Prepend a (key) + value to the current array.
5075
     *
5076
     * EXAMPLE: <code>
5077
     * a(['fΓ²Γ΄' => 'bΓ Ε™'])->prepend('foo'); // Arrayy[0 => 'foo', 'fΓ²Γ΄' => 'bΓ Ε™']
5078
     * </code>
5079
     *
5080
     * @param mixed $value
5081
     * @param mixed $key
5082
     *
5083
     * @return $this
5084
     *               <p>(Mutable) Return this Arrayy object, with the prepended value.</p>
5085
     *
5086
     * @phpstan-param T $value
5087
     * @phpstan-param TKey|null $key
5088
     * @phpstan-return static
5089
     */
5090
    public function prepend($value, $key = null)
5091
    {
5092
        $this->generatorToArray();
84 ✔
5093

5094
        if ($this->properties !== []) {
84 ✔
5095
            $this->checkType($key, $value);
28 ✔
5096
        }
5097

5098
        if ($key === null) {
70 ✔
5099
            \array_unshift($this->array, $value);
56 ✔
5100
        } else {
5101
            $this->array = [$key => $value] + $this->array; // @phpstan-ignore assign.propertyType
21 ✔
5102
        }
5103

5104
        return $this;
70 ✔
5105
    }
5106

5107
    /**
5108
     * Prepend a (key) + value to the current array.
5109
     *
5110
     * EXAMPLE: <code>
5111
     * a(['fΓ²Γ΄' => 'bΓ Ε™'])->prependImmutable('foo')->getArray(); // [0 => 'foo', 'fΓ²Γ΄' => 'bΓ Ε™']
5112
     * </code>
5113
     *
5114
     * @param mixed $value
5115
     * @param mixed $key
5116
     *
5117
     * @return $this
5118
     *               <p>(Immutable) Return this Arrayy object, with the prepended value.</p>
5119
     *
5120
     * @phpstan-param T $value
5121
     * @phpstan-param TKey $key
5122
     * @phpstan-return static
5123
     * @psalm-mutation-free
5124
     */
5125
    public function prependImmutable($value, $key = null)
5126
    {
5127
        $generator = function () use ($key, $value): \Generator {
7 ✔
5128
            if ($this->properties !== []) {
7 ✔
5129
                $this->checkType($key, $value);
×
5130
            }
5131

5132
            if ($key !== null) {
7 ✔
5133
                yield $key => $value;
×
5134
            } else {
5135
                yield $value;
7 ✔
5136
            }
5137

5138
            foreach ($this->getGenerator() as $keyOld => $itemOld) {
7 ✔
5139
                yield $keyOld => $itemOld;
7 ✔
5140
            }
5141
        };
7 ✔
5142

5143
        return static::create(
7 ✔
5144
            $generator,
7 ✔
5145
            $this->iteratorClass,
7 ✔
5146
            false
7 ✔
5147
        );
7 ✔
5148
    }
5149

5150
    /**
5151
     * Add a suffix to each key.
5152
     *
5153
     * @param float|int|string $suffix
5154
     *
5155
     * @return static
5156
     *                <p>(Immutable) Return an Arrayy object, with the prepended keys.</p>
5157
     *
5158
     * @phpstan-return static
5159
     * @psalm-mutation-free
5160
     */
5161
    public function prependToEachKey($suffix): self
5162
    {
5163
        // init
5164
        $result = [];
70 ✔
5165

5166
        foreach ($this->getGenerator() as $key => $item) {
70 ✔
5167
            if ($item instanceof self) {
63 ✔
5168
                $result[$key] = $item->prependToEachKey($suffix);
×
5169
            } elseif (\is_array($item)) {
63 ✔
5170
                $result[$key] = self::create(
×
5171
                    $item,
×
5172
                    $this->iteratorClass,
×
5173
                    false
×
5174
                )->prependToEachKey($suffix)
×
5175
                    ->toArray();
×
5176
            } else {
5177
                $result[$key . $suffix] = $item;
63 ✔
5178
            }
5179
        }
5180

5181
        return self::create(
70 ✔
5182
            $result,
70 ✔
5183
            $this->iteratorClass,
70 ✔
5184
            false
70 ✔
5185
        );
70 ✔
5186
    }
5187

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

5204
        foreach ($this->getGenerator() as $key => $item) {
70 ✔
5205
            if ($item instanceof self) {
63 ✔
5206
                $result[$key] = $item->prependToEachValue($suffix);
×
5207
            } elseif (\is_array($item)) {
63 ✔
5208
                $result[$key] = self::create(
×
5209
                    $item,
×
5210
                    $this->iteratorClass,
×
5211
                    false
×
5212
                )->prependToEachValue($suffix)
×
5213
                    ->toArray();
×
5214
            } elseif (\is_object($item) === true) {
63 ✔
5215
                $result[$key] = $item;
7 ✔
5216
            } else {
5217
                $result[$key] = $item . $suffix;
56 ✔
5218
            }
5219
        }
5220

5221
        return self::create(
70 ✔
5222
            $result,
70 ✔
5223
            $this->iteratorClass,
70 ✔
5224
            false
70 ✔
5225
        );
70 ✔
5226
    }
5227

5228
    /**
5229
     * Return the value of a given key and
5230
     * delete the key.
5231
     *
5232
     * @param int|int[]|string|string[]|null $keyOrKeys
5233
     * @param mixed                          $fallback
5234
     *
5235
     * @return mixed
5236
     *
5237
     * @template TFallback $fallback
5238
     * @phpstan-param TFallback $fallback
5239
     * @phpstan-return TFallback|T|T[]
5240
     */
5241
    public function pull($keyOrKeys = null, $fallback = null)
5242
    {
5243
        if ($keyOrKeys === null) {
42 ✔
5244
            $array = $this->toArray();
7 ✔
5245
            $this->clear();
7 ✔
5246

5247
            return $array;
7 ✔
5248
        }
5249

5250
        if (\is_array($keyOrKeys)) {
35 ✔
5251
            $valueOrValues = [];
7 ✔
5252
            foreach ($keyOrKeys as $key) {
7 ✔
5253
                $valueOrValues[] = $this->get($key, $fallback);
7 ✔
5254
                $this->offsetUnset($key);
7 ✔
5255
            }
5256
        } else {
5257
            $valueOrValues = $this->get($keyOrKeys, $fallback);
35 ✔
5258
            $this->offsetUnset($keyOrKeys);
35 ✔
5259
        }
5260

5261
        /** @var T|T[]|TFallback $valueOrValues */
5262
        return $valueOrValues;
35 ✔
5263
    }
5264

5265
    /**
5266
     * Push one or more values onto the end of array at once.
5267
     *
5268
     * @param mixed ...$args
5269
     *
5270
     * @return $this
5271
     *               <p>(Mutable) Return this Arrayy object, with pushed elements to the end of array.</p>
5272
     *
5273
     * @noinspection ReturnTypeCanBeDeclaredInspection
5274
     *
5275
     * @phpstan-param  array<TKey,T> ...$args
5276
     * @phpstan-return static
5277
     */
5278
    public function push(...$args)
5279
    {
5280
        $this->generatorToArray();
63 ✔
5281

5282
        if (
5283
            $this->checkPropertyTypes
63 ✔
5284
            &&
5285
            $this->properties !== []
63 ✔
5286
        ) {
5287
            foreach ($args as $key => $value) {
21 ✔
5288
                $this->checkType($key, $value);
21 ✔
5289
            }
5290
        }
5291

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

5294
        return $this;
56 ✔
5295
    }
5296

5297
    /**
5298
     * Get a random value from the current array.
5299
     *
5300
     * EXAMPLE: <code>
5301
     * a([1, 2, 3, 4])->randomImmutable(2); // e.g.: Arrayy[1, 4]
5302
     * </code>
5303
     *
5304
     * @param int|null $number <p>How many values you will take?</p>
5305
     *
5306
     * @return static
5307
     *                <p>(Immutable)</p>
5308
     *
5309
     * @phpstan-return static
5310
     */
5311
    public function randomImmutable(?int $number = null): self
5312
    {
5313
        $this->generatorToArray();
133 ✔
5314

5315
        if ($this->count() === 0) {
133 ✔
5316
            return static::create(
7 ✔
5317
                [],
7 ✔
5318
                $this->iteratorClass,
7 ✔
5319
                false
7 ✔
5320
            );
7 ✔
5321
        }
5322

5323
        if ($number === null) {
126 ✔
5324
            $arrayRandValue = [$this->array[\array_rand($this->array)]];
91 ✔
5325

5326
            return static::create(
91 ✔
5327
                $arrayRandValue,
91 ✔
5328
                $this->iteratorClass,
91 ✔
5329
                false
91 ✔
5330
            );
91 ✔
5331
        }
5332

5333
        $arrayTmp = $this->array;
42 ✔
5334
        \shuffle($arrayTmp);
42 ✔
5335

5336
        return static::create(
42 ✔
5337
            $arrayTmp,
42 ✔
5338
            $this->iteratorClass,
42 ✔
5339
            false
42 ✔
5340
        )->firstsImmutable($number);
42 ✔
5341
    }
5342

5343
    /**
5344
     * Pick a random key/index from the keys of this array.
5345
     *
5346
     * EXAMPLE: <code>
5347
     * $arrayy = A::create([1 => 'one', 2 => 'two']);
5348
     * $arrayy->randomKey(); // e.g. 2
5349
     * </code>
5350
     *
5351
     * @throws \RangeException If array is empty
5352
     *
5353
     * @return mixed|null
5354
     *                    <p>Get a key/index or null if there wasn't a key/index.</p>
5355
     *
5356
     * @phpstan-return null|TKey
5357
     */
5358
    public function randomKey()
5359
    {
5360
        $result = $this->randomKeys(1);
28 ✔
5361

5362
        if (!isset($result[0])) {
28 ✔
5363
            $result[0] = null;
×
5364
        }
5365

5366
        return $result[0];
28 ✔
5367
    }
5368

5369
    /**
5370
     * Pick a given number of random keys/indexes out of this array.
5371
     *
5372
     * EXAMPLE: <code>
5373
     * a([1 => 'one', 2 => 'two'])->randomKeys(); // e.g. Arrayy[1, 2]
5374
     * </code>
5375
     *
5376
     * @param int $number <p>The number of keys/indexes (should be <= \count($this->array))</p>
5377
     *
5378
     * @throws \RangeException If array is empty
5379
     *
5380
     * @return static
5381
     *                <p>(Immutable)</p>
5382
     *
5383
     * @phpstan-return static
5384
     */
5385
    public function randomKeys(int $number): self
5386
    {
5387
        $this->generatorToArray();
91 ✔
5388

5389
        $count = $this->count();
91 ✔
5390

5391
        if (
5392
            $number === 0
91 ✔
5393
            ||
5394
            $number > $count
91 ✔
5395
        ) {
5396
            throw new \RangeException(
14 ✔
5397
                \sprintf(
14 ✔
5398
                    'Number of requested keys (%s) must be equal or lower than number of elements in this array (%s)',
14 ✔
5399
                    $number,
14 ✔
5400
                    $count
14 ✔
5401
                )
14 ✔
5402
            );
14 ✔
5403
        }
5404

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

5407
        return static::create(
77 ✔
5408
            $result,
77 ✔
5409
            $this->iteratorClass,
77 ✔
5410
            false
77 ✔
5411
        );
77 ✔
5412
    }
5413

5414
    /**
5415
     * Get a random value from the current array.
5416
     *
5417
     * EXAMPLE: <code>
5418
     * a([1, 2, 3, 4])->randomMutable(2); // e.g.: Arrayy[1, 4]
5419
     * </code>
5420
     *
5421
     * @param int|null $number <p>How many values you will take?</p>
5422
     *
5423
     * @return $this
5424
     *               <p>(Mutable) Return this Arrayy object.</p>
5425
     *
5426
     * @phpstan-return static
5427
     */
5428
    public function randomMutable(?int $number = null): self
5429
    {
5430
        $this->generatorToArray();
119 ✔
5431

5432
        if ($this->count() === 0) {
119 ✔
5433
            return static::create(
×
5434
                [],
×
5435
                $this->iteratorClass,
×
5436
                false
×
5437
            );
×
5438
        }
5439

5440
        if ($number === null) {
119 ✔
5441
            $arrayRandValue = [$this->array[\array_rand($this->array)]];
49 ✔
5442
            $this->array = $arrayRandValue; // @phpstan-ignore assign.propertyType
49 ✔
5443

5444
            return $this;
49 ✔
5445
        }
5446

5447
        \shuffle($this->array);
77 ✔
5448

5449
        return $this->firstsMutable($number);
77 ✔
5450
    }
5451

5452
    /**
5453
     * Pick a random value from the values of this array.
5454
     *
5455
     * EXAMPLE: <code>
5456
     * a([1 => 'one', 2 => 'two'])->randomValue(); // e.g. 'one'
5457
     * </code>
5458
     *
5459
     * @return mixed
5460
     *               <p>Get a random value or null if there wasn't a value.</p>
5461
     *
5462
     * @phpstan-return T|null
5463
     */
5464
    public function randomValue()
5465
    {
5466
        $result = $this->randomImmutable();
28 ✔
5467

5468
        if (!isset($result[0])) {
28 ✔
5469
            $result[0] = null;
×
5470
        }
5471

5472
        return $result[0];
28 ✔
5473
    }
5474

5475
    /**
5476
     * Pick a given number of random values out of this array.
5477
     *
5478
     * EXAMPLE: <code>
5479
     * a([1 => 'one', 2 => 'two'])->randomValues(); // e.g. Arrayy['one', 'two']
5480
     * </code>
5481
     *
5482
     * @param int $number
5483
     *
5484
     * @return static
5485
     *                <p>(Mutable)</p>
5486
     *
5487
     * @phpstan-return static
5488
     */
5489
    public function randomValues(int $number): self
5490
    {
5491
        return $this->randomMutable($number);
49 ✔
5492
    }
5493

5494
    /**
5495
     * Get a random value from an array, with the ability to skew the results.
5496
     *
5497
     * EXAMPLE: <code>
5498
     * a([0 => 3, 1 => 4])->randomWeighted([1 => 4]); // e.g.: Arrayy[4] (has a 66% chance of returning 4)
5499
     * </code>
5500
     *
5501
     * @param array    $array
5502
     * @param int|null $number <p>How many values you will take?</p>
5503
     *
5504
     * @return static
5505
     *                           <p>(Immutable)</p>
5506
     *
5507
     * @phpstan-param  array<(int&T)|(string&T),int> $array
5508
     * @phpstan-return static
5509
     */
5510
    public function randomWeighted(array $array, ?int $number = null): self
5511
    {
5512
        // init
5513
        $options = [];
63 ✔
5514

5515
        foreach ($array as $option => $weight) {
63 ✔
5516
            /** @phpstan-var T $option - hack: we protect this method via (int&T)|(string&T) */
5517
            if ($this->searchIndex($option) !== false) {
63 ✔
5518
                for ($i = 0; $i < $weight; ++$i) {
14 ✔
5519
                    $options[] = $option;
7 ✔
5520
                }
5521
            }
5522
        }
5523

5524
        return $this->mergeAppendKeepIndex($options)->randomImmutable($number);
63 ✔
5525
    }
5526

5527
    /**
5528
     * Reduce the current array via callable e.g. anonymous-function and return the end result.
5529
     *
5530
     * EXAMPLE: <code>
5531
     * a([1, 2, 3, 4])->reduce(
5532
     *     function ($carry, $item) {
5533
     *         return $carry * $item;
5534
     *     },
5535
     *     1
5536
     * ); // Arrayy[24]
5537
     * </code>
5538
     *
5539
     * @param callable $callable
5540
     * @param mixed    $initial
5541
     *
5542
     * @return static
5543
     *                <p>(Immutable)</p>
5544
     *
5545
     * @template T2
5546
     *              <p>The output value type.</p>
5547
     *
5548
     * @phpstan-param callable(T2, T, TKey): T2 $callable
5549
     * @phpstan-param T2                        $initial
5550
     *
5551
     * @phpstan-return static
5552
     * @psalm-mutation-free
5553
     */
5554
    public function reduce($callable, $initial = []): self
5555
    {
5556
        foreach ($this->getGenerator() as $key => $value) {
126 ✔
5557
            $initial = $callable($initial, $value, $key);
119 ✔
5558
        }
5559

5560
        /** @var static $return - help for phpstan */
5561
        $return = static::create(
126 ✔
5562
            $initial,
126 ✔
5563
            $this->iteratorClass,
126 ✔
5564
            false
126 ✔
5565
        );
126 ✔
5566

5567
        return $return;
126 ✔
5568
    }
5569

5570
    /**
5571
     * @param bool $unique
5572
     *
5573
     * @return static
5574
     *                <p>(Immutable)</p>
5575
     *
5576
     * @phpstan-return static
5577
     * @psalm-mutation-free
5578
     */
5579
    public function reduce_dimension(bool $unique = true): self
5580
    {
5581
        // init
5582
        $result = [];
98 ✔
5583

5584
        foreach ($this->getGenerator() as $val) {
98 ✔
5585
            if (\is_array($val)) {
84 ✔
5586
                $result[] = static::create($val)->reduce_dimension($unique)->toArray();
35 ✔
5587
            } else {
5588
                $result[] = [$val];
84 ✔
5589
            }
5590
        }
5591

5592
        $result = $result === [] ? [] : \array_merge(...$result);
98 ✔
5593

5594
        $resultArrayy = static::create($result);
98 ✔
5595

5596
        /**
5597
         * @psalm-suppress ImpureMethodCall - object is already re-created
5598
         * @psalm-suppress InvalidReturnStatement - why?
5599
         */
5600
        return $unique ? $resultArrayy->unique() : $resultArrayy;
98 ✔
5601
    }
5602

5603
    /**
5604
     * Create a numerically re-indexed Arrayy object.
5605
     *
5606
     * EXAMPLE: <code>
5607
     * a([2 => 1, 3 => 2])->reindex(); // Arrayy[0 => 1, 1 => 2]
5608
     * </code>
5609
     *
5610
     * @return $this
5611
     *               <p>(Mutable) Return this Arrayy object, with re-indexed array-elements.</p>
5612
     *
5613
     * @phpstan-return static
5614
     */
5615
    public function reindex(): self
5616
    {
5617
        $this->generatorToArray(false);
63 ✔
5618

5619
        $this->array = \array_values($this->array);
63 ✔
5620

5621
        return $this;
63 ✔
5622
    }
5623

5624
    /**
5625
     * Return all items that fail the truth test.
5626
     *
5627
     * EXAMPLE: <code>
5628
     * $closure = function ($value) {
5629
     *     return $value % 2 !== 0;
5630
     * }
5631
     * a([1, 2, 3, 4])->reject($closure); // Arrayy[1 => 2, 3 => 4]
5632
     * </code>
5633
     *
5634
     * @param \Closure $closure
5635
     *
5636
     * @return static
5637
     *                <p>(Immutable)</p>
5638
     *
5639
     * @phpstan-param \Closure(T,TKey):bool  $closure
5640
     * @phpstan-return static
5641
     * @psalm-mutation-free
5642
     */
5643
    public function reject(\Closure $closure): self
5644
    {
5645
        // init
5646
        $filtered = [];
7 ✔
5647

5648
        foreach ($this->getGenerator() as $key => $value) {
7 ✔
5649
            if (!$closure($value, $key)) {
7 ✔
5650
                $filtered[$key] = $value;
7 ✔
5651
            }
5652
        }
5653

5654
        return static::create(
7 ✔
5655
            $filtered,
7 ✔
5656
            $this->iteratorClass,
7 ✔
5657
            false
7 ✔
5658
        );
7 ✔
5659
    }
5660

5661
    /**
5662
     * Remove a value from the current array (optional using dot-notation).
5663
     *
5664
     * EXAMPLE: <code>
5665
     * a([1 => 'bar', 'foo' => 'foo'])->remove(1); // Arrayy['foo' => 'foo']
5666
     * </code>
5667
     *
5668
     * @param mixed $key
5669
     *
5670
     * @return static
5671
     *                <p>(Mutable)</p>
5672
     *
5673
     * @phpstan-param  TKey|TKey[] $key
5674
     * @phpstan-return static
5675
     */
5676
    public function remove($key)
5677
    {
5678
        // recursive call
5679
        if (\is_array($key)) {
154 ✔
5680
            foreach ($key as $k) {
7 ✔
5681
                $this->internalRemove($k);
7 ✔
5682
            }
5683

5684
            return static::create(
7 ✔
5685
                $this->toArray(),
7 ✔
5686
                $this->iteratorClass,
7 ✔
5687
                false
7 ✔
5688
            );
7 ✔
5689
        }
5690

5691
        $this->internalRemove($key);
147 ✔
5692

5693
        return static::create(
147 ✔
5694
            $this->toArray(),
147 ✔
5695
            $this->iteratorClass,
147 ✔
5696
            false
147 ✔
5697
        );
147 ✔
5698
    }
5699

5700
    /**
5701
     * alias: for "Arrayy->removeValue()"
5702
     *
5703
     * @param mixed $element
5704
     *
5705
     * @return static
5706
     *                <p>(Immutable)</p>
5707
     *
5708
     * @phpstan-param  T $element
5709
     * @phpstan-return static
5710
     * @psalm-mutation-free
5711
     */
5712
    public function removeElement($element)
5713
    {
5714
        return $this->removeValue($element);
56 ✔
5715
    }
5716

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

5734
        \array_shift($tmpArray);
49 ✔
5735

5736
        return static::create(
49 ✔
5737
            $tmpArray,
49 ✔
5738
            $this->iteratorClass,
49 ✔
5739
            false
49 ✔
5740
        );
49 ✔
5741
    }
5742

5743
    /**
5744
     * Remove the last value from the current array.
5745
     *
5746
     * EXAMPLE: <code>
5747
     * a([1 => 'bar', 'foo' => 'foo'])->removeLast(); // Arrayy[1 => 'bar']
5748
     * </code>
5749
     *
5750
     * @return static
5751
     *                <p>(Immutable)</p>
5752
     *
5753
     * @phpstan-return static
5754
     * @psalm-mutation-free
5755
     */
5756
    public function removeLast(): self
5757
    {
5758
        $tmpArray = $this->toArray();
49 ✔
5759

5760
        \array_pop($tmpArray);
49 ✔
5761

5762
        return static::create(
49 ✔
5763
            $tmpArray,
49 ✔
5764
            $this->iteratorClass,
49 ✔
5765
            false
49 ✔
5766
        );
49 ✔
5767
    }
5768

5769
    /**
5770
     * Removes a particular value from an array (numeric or associative).
5771
     *
5772
     * EXAMPLE: <code>
5773
     * a([1 => 'bar', 'foo' => 'foo'])->removeValue('foo'); // Arrayy[1 => 'bar']
5774
     * </code>
5775
     *
5776
     * @param mixed $value
5777
     *
5778
     * @return static
5779
     *                <p>(Immutable)</p>
5780
     *
5781
     * @phpstan-param  T $value
5782
     * @phpstan-return static
5783
     * @psalm-mutation-free
5784
     */
5785
    public function removeValue($value): self
5786
    {
5787
        $this->generatorToArray();
56 ✔
5788

5789
        // init
5790
        $isSequentialArray = $this->isSequential();
56 ✔
5791

5792
        foreach ($this->array as $key => $item) {
56 ✔
5793
            if ($item === $value) {
49 ✔
5794
                unset($this->array[$key]);
49 ✔
5795
            }
5796
        }
5797

5798
        if ($isSequentialArray) {
56 ✔
5799
            $this->array = \array_values($this->array);
42 ✔
5800
        }
5801

5802
        return static::create(
56 ✔
5803
            $this->array,
56 ✔
5804
            $this->iteratorClass,
56 ✔
5805
            false
56 ✔
5806
        );
56 ✔
5807
    }
5808

5809
    /**
5810
     * Generate array of repeated arrays.
5811
     *
5812
     * @param int $times <p>How many times has to be repeated.</p>
5813
     *
5814
     * @return static
5815
     *                <p>(Immutable)</p>
5816
     *
5817
     * @phpstan-return static
5818
     * @psalm-mutation-free
5819
     */
5820
    public function repeat($times): self
5821
    {
5822
        if ($times === 0) {
7 ✔
5823
            return static::create([], $this->iteratorClass);
7 ✔
5824
        }
5825

5826
        return static::create(
7 ✔
5827
            \array_fill(0, (int) $times, $this->toArray()),
7 ✔
5828
            $this->iteratorClass,
7 ✔
5829
            false
7 ✔
5830
        );
7 ✔
5831
    }
5832

5833
    /**
5834
     * Replace a key with a new key/value pair.
5835
     *
5836
     * EXAMPLE: <code>
5837
     * $arrayy = a([1 => 'foo', 2 => 'foo2', 3 => 'bar']);
5838
     * $arrayy->replace(2, 'notfoo', 'notbar'); // Arrayy[1 => 'foo', 'notfoo' => 'notbar', 3 => 'bar']
5839
     * </code>
5840
     *
5841
     * @param mixed $oldKey
5842
     * @param mixed $newKey
5843
     * @param mixed $newValue
5844
     *
5845
     * @return static
5846
     *                <p>(Immutable)</p>
5847
     *
5848
     * @phpstan-param TKey $oldKey
5849
     * @phpstan-param TKey $newKey
5850
     * @phpstan-param T $newValue
5851
     * @phpstan-return static
5852
     * @psalm-mutation-free
5853
     */
5854
    public function replace($oldKey, $newKey, $newValue): self
5855
    {
5856
        $that = clone $this;
35 ✔
5857

5858
        /**
5859
         * @psalm-suppress ImpureMethodCall - object is already cloned
5860
         */
5861
        return $that->remove($oldKey)
35 ✔
5862
            ->set($newKey, $newValue);
35 ✔
5863
    }
5864

5865
    /**
5866
     * Create an array using the current array as values and the other array as keys.
5867
     *
5868
     * EXAMPLE: <code>
5869
     * $firstArray = [
5870
     *     1 => 'one',
5871
     *     2 => 'two',
5872
     *     3 => 'three',
5873
     * ];
5874
     * $secondArray = [
5875
     *     'one' => 1,
5876
     *     1     => 'one',
5877
     *     2     => 2,
5878
     * ];
5879
     * $arrayy = a($firstArray);
5880
     * $arrayy->replaceAllKeys($secondArray); // Arrayy[1 => "one", 'one' => "two", 2 => "three"]
5881
     * </code>
5882
     *
5883
     * @param int[]|string[] $keys <p>An array of keys.</p>
5884
     *
5885
     * @return static
5886
     *                <p>(Immutable) Arrayy object with keys from the other array, empty Arrayy object if the number of elements
5887
     *                for each array isn't equal or if the arrays are empty.
5888
     *                </p>
5889
     *
5890
     * @phpstan-param  array<TKey> $keys
5891
     * @phpstan-return static
5892
     * @psalm-mutation-free
5893
     */
5894
    public function replaceAllKeys(array $keys): self
5895
    {
5896
        $data = \array_combine($keys, $this->toArray());
14 ✔
5897
        /* @phpstan-ignore-next-line | change from PHP >= 8, but we still support old versions */
5898
        if ($data === false) {
14 ✔
5899
            $data = [];
×
5900
        }
5901

5902
        return static::create(
14 ✔
5903
            $data,
14 ✔
5904
            $this->iteratorClass,
14 ✔
5905
            false
14 ✔
5906
        );
14 ✔
5907
    }
5908

5909
    /**
5910
     * Create an array using the current array as keys and the other array as values.
5911
     *
5912
     * EXAMPLE: <code>
5913
     * $firstArray = [
5914
     *     1 => 'one',
5915
     *     2 => 'two',
5916
     *     3 => 'three',
5917
     * ];
5918
     * $secondArray = [
5919
     *     'one' => 1,
5920
     *     1     => 'one',
5921
     *     2     => 2,
5922
     * ];
5923
     * $arrayy = a($firstArray);
5924
     * $arrayy->replaceAllValues($secondArray); // Arrayy['one' => 1, 'two' => 'one', 'three' => 2]
5925
     * </code>
5926
     *
5927
     * @param array $array <p>An array of values.</p>
5928
     *
5929
     * @return static
5930
     *                <p>(Immutable) Arrayy object with values from the other array, empty Arrayy object if the number of elements
5931
     *                for each array isn't equal or if the arrays are empty.
5932
     *                </p>
5933
     *
5934
     * @phpstan-param  array<T> $array
5935
     * @phpstan-return static
5936
     * @psalm-mutation-free
5937
     */
5938
    public function replaceAllValues(array $array): self
5939
    {
5940
        $data = \array_combine($this->toArray(), $array);
14 ✔
5941
        /* @phpstan-ignore-next-line | change from PHP >= 8, but we still support old versions */
5942
        if ($data === false) {
14 ✔
5943
            $data = [];
×
5944
        }
5945

5946
        return static::create(
14 ✔
5947
            $data,
14 ✔
5948
            $this->iteratorClass,
14 ✔
5949
            false
14 ✔
5950
        );
14 ✔
5951
    }
5952

5953
    /**
5954
     * Replace the keys in an array with another set.
5955
     *
5956
     * EXAMPLE: <code>
5957
     * a([1 => 'bar', 'foo' => 'foo'])->replaceKeys([1 => 2, 'foo' => 'replaced']); // Arrayy[2 => 'bar', 'replaced' => 'foo']
5958
     * </code>
5959
     *
5960
     * @param array $keys <p>An array of keys matching the array's size.</p>
5961
     *
5962
     * @return static
5963
     *                <p>(Immutable)</p>
5964
     *
5965
     * @phpstan-param  array<TKey> $keys
5966
     * @phpstan-return static
5967
     * @psalm-mutation-free
5968
     */
5969
    public function replaceKeys(array $keys): self
5970
    {
5971
        $values = \array_values($this->toArray());
7 ✔
5972
        $result = \array_combine($keys, $values);
7 ✔
5973
        /* @phpstan-ignore-next-line | change from PHP >= 8, but we still support old versions */
5974
        if ($result === false) {
7 ✔
5975
            $result = [];
×
5976
        }
5977

5978
        return static::create(
7 ✔
5979
            $result,
7 ✔
5980
            $this->iteratorClass,
7 ✔
5981
            false
7 ✔
5982
        );
7 ✔
5983
    }
5984

5985
    /**
5986
     * Replace the first matched value in an array.
5987
     *
5988
     * EXAMPLE: <code>
5989
     * $testArray = ['bar', 'foo' => 'foo', 'foobar' => 'foobar'];
5990
     * a($testArray)->replaceOneValue('foo', 'replaced'); // Arrayy['bar', 'foo' => 'replaced', 'foobar' => 'foobar']
5991
     * </code>
5992
     *
5993
     * @param mixed $search      <p>The value to replace.</p>
5994
     * @param mixed $replacement <p>The value to replace.</p>
5995
     *
5996
     * @return static
5997
     *                <p>(Immutable)</p>
5998
     *
5999
     * @phpstan-param T $search
6000
     * @phpstan-param T $replacement
6001
     * @phpstan-return static
6002
     * @psalm-mutation-free
6003
     */
6004
    public function replaceOneValue($search, $replacement = ''): self
6005
    {
6006
        $array = $this->toArray();
21 ✔
6007
        $key = \array_search($search, $array, true);
21 ✔
6008

6009
        if ($key !== false) {
21 ✔
6010
            $array[$key] = $replacement;
21 ✔
6011
        }
6012

6013
        return static::create(
21 ✔
6014
            $array,
21 ✔
6015
            $this->iteratorClass,
21 ✔
6016
            false
21 ✔
6017
        );
21 ✔
6018
    }
6019

6020
    /**
6021
     * Replace values in the current array.
6022
     *
6023
     * EXAMPLE: <code>
6024
     * $testArray = ['bar', 'foo' => 'foo', 'foobar' => 'foobar'];
6025
     * a($testArray)->replaceValues('foo', 'replaced'); // Arrayy['bar', 'foo' => 'replaced', 'foobar' => 'replacedbar']
6026
     * </code>
6027
     *
6028
     * @param string $search      <p>The value to replace.</p>
6029
     * @param string $replacement <p>What to replace it with.</p>
6030
     *
6031
     * @return static
6032
     *                <p>(Immutable)</p>
6033
     *
6034
     * @phpstan-return static
6035
     * @psalm-mutation-free
6036
     */
6037
    public function replaceValues($search, $replacement = ''): self
6038
    {
6039
        $callable = static function ($value) use ($search, $replacement) {
7 ✔
6040
            return \str_replace($search, $replacement, $value);
7 ✔
6041
        };
7 ✔
6042

6043
        /* @phpstan-ignore-next-line | ignore Closure with one or two parameters, is this a bug in phpstan? */
6044
        return $this->each($callable);
7 ✔
6045
    }
6046

6047
    /**
6048
     * Get the last elements from index $from until the end of this array.
6049
     *
6050
     * EXAMPLE: <code>
6051
     * a([2 => 'foo', 3 => 'bar', 4 => 'lall'])->rest(2); // Arrayy[0 => 'lall']
6052
     * </code>
6053
     *
6054
     * @param int $from
6055
     *
6056
     * @return static
6057
     *                <p>(Immutable)</p>
6058
     *
6059
     * @phpstan-return static
6060
     * @psalm-mutation-free
6061
     */
6062
    public function rest(int $from = 1): self
6063
    {
6064
        $tmpArray = $this->toArray();
105 ✔
6065

6066
        return static::create(
105 ✔
6067
            \array_splice($tmpArray, $from),
105 ✔
6068
            $this->iteratorClass,
105 ✔
6069
            false
105 ✔
6070
        );
105 ✔
6071
    }
6072

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

6089
        $this->array = \array_reverse($this->array);
63 ✔
6090

6091
        return $this;
63 ✔
6092
    }
6093

6094
    /**
6095
     * Return the array with keys in the reverse order.
6096
     *
6097
     * EXAMPLE: <code>
6098
     * a([1 => 1, 2 => 2, 3 => 3])->reverse(); // self[3 => 3, 2 => 2, 1 => 1]
6099
     * </code>
6100
     *
6101
     * @return $this
6102
     *               <p>(Mutable) Return this Arrayy object.</p>
6103
     *
6104
     * @phpstan-return static
6105
     */
6106
    public function reverseKeepIndex(): self
6107
    {
6108
        $this->generatorToArray();
35 ✔
6109

6110
        $this->array = \array_reverse($this->array, true);
35 ✔
6111

6112
        return $this;
35 ✔
6113
    }
6114

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

6133
        \rsort($this->array, $sort_flags);
28 ✔
6134

6135
        return $this;
28 ✔
6136
    }
6137

6138
    /**
6139
     * Sort an array in reverse order.
6140
     *
6141
     * @param int $sort_flags [optional] <p>
6142
     *                        You may modify the behavior of the sort using the optional
6143
     *                        parameter sort_flags, for details
6144
     *                        see sort.
6145
     *                        </p>
6146
     *
6147
     * @return $this
6148
     *               <p>(Immutable) Return this Arrayy object.</p>
6149
     *
6150
     * @phpstan-return static
6151
     * @psalm-mutation-free
6152
     */
6153
    public function rsortImmutable(int $sort_flags = 0): self
6154
    {
6155
        $that = clone $this;
28 ✔
6156

6157
        /**
6158
         * @psalm-suppress ImpureMethodCall - object is already cloned
6159
         */
6160
        $that->rsort($sort_flags);
28 ✔
6161

6162
        return $that;
28 ✔
6163
    }
6164

6165
    /**
6166
     * Search for the first index of the current array via $value.
6167
     *
6168
     * EXAMPLE: <code>
6169
     * a(['fΓ²Γ΄' => 'bΓ Ε™', 'lall' => 'bΓ Ε™'])->searchIndex('bΓ Ε™'); // Arrayy[0 => 'fΓ²Γ΄']
6170
     * </code>
6171
     *
6172
     * @param mixed $value
6173
     *
6174
     * @return false|int|string
6175
     *                          <p>Will return <b>FALSE</b> if the value can't be found.</p>
6176
     *
6177
     * @phpstan-param T $value
6178
     * @phpstan-return false|TKey
6179
     *
6180
     * @psalm-mutation-free
6181
     */
6182
    public function searchIndex($value)
6183
    {
6184
        foreach ($this->getGenerator() as $keyFromArray => $valueFromArray) {
147 ✔
6185
            if ($value === $valueFromArray) {
140 ✔
6186
                return $keyFromArray;
70 ✔
6187
            }
6188
        }
6189

6190
        return false;
77 ✔
6191
    }
6192

6193
    /**
6194
     * Search for the value of the current array via $index.
6195
     *
6196
     * EXAMPLE: <code>
6197
     * a(['fΓ²Γ΄' => 'bΓ Ε™'])->searchValue('fΓ²Γ΄'); // Arrayy[0 => 'bΓ Ε™']
6198
     * </code>
6199
     *
6200
     * @param mixed $index
6201
     *
6202
     * @return static
6203
     *                <p>(Immutable) Will return a empty Arrayy if the value wasn't found.</p>
6204
     *
6205
     * @phpstan-param TKey $index
6206
     * @phpstan-return static
6207
     * @psalm-mutation-free
6208
     */
6209
    public function searchValue($index): self
6210
    {
6211
        $this->generatorToArray();
63 ✔
6212

6213
        // init
6214
        $return = [];
63 ✔
6215

6216
        if ($this->array === []) {
63 ✔
6217
            return static::create(
×
6218
                [],
×
6219
                $this->iteratorClass,
×
6220
                false
×
6221
            );
×
6222
        }
6223

6224
        // php cast "bool"-index into "int"-index
6225
        /* @phpstan-ignore-next-line | array-key helper */
6226
        if ((bool) $index === $index) {
63 ✔
6227
            $index = (int) $index;
7 ✔
6228
        }
6229

6230
        if ($this->offsetExists($index)) {
63 ✔
6231
            $return = [$this->array[$index]];
49 ✔
6232
        }
6233

6234
        return static::create(
63 ✔
6235
            $return,
63 ✔
6236
            $this->iteratorClass,
63 ✔
6237
            false
63 ✔
6238
        );
63 ✔
6239
    }
6240

6241
    /**
6242
     * Set a value for the current array (optional using dot-notation).
6243
     *
6244
     * EXAMPLE: <code>
6245
     * $arrayy = a(['Lars' => ['lastname' => 'Moelleken']]);
6246
     * $arrayy->set('Lars.lastname', 'MΓΌller'); // Arrayy['Lars', ['lastname' => 'MΓΌller']]]
6247
     * </code>
6248
     *
6249
     * @param string $key   <p>The key to set.</p>
6250
     * @param mixed  $value <p>Its value.</p>
6251
     *
6252
     * @return $this
6253
     *               <p>(Mutable) Return this Arrayy object.</p>
6254
     *
6255
     * @phpstan-param  TKey $key
6256
     * @phpstan-param  T $value
6257
     * @phpstan-return static
6258
     */
6259
    public function set($key, $value): self
6260
    {
6261
        $this->internalSet($key, $value);
203 ✔
6262

6263
        return $this;
196 ✔
6264
    }
6265

6266
    /**
6267
     * Get a value from a array and set it if it was not.
6268
     *
6269
     * WARNING: this method only set the value, if the $key is not already set
6270
     *
6271
     * EXAMPLE: <code>
6272
     * $arrayy = a([1 => 1, 2 => 2, 3 => 3]);
6273
     * $arrayy->setAndGet(1, 4); // 1
6274
     * $arrayy->setAndGet(0, 4); // 4
6275
     * </code>
6276
     *
6277
     * @param mixed $key      <p>The key</p>
6278
     * @param mixed $fallback <p>The default value to set if it isn't.</p>
6279
     *
6280
     * @return mixed
6281
     *               <p>(Mutable)</p>
6282
     *
6283
     * @phpstan-param TKey $key
6284
     * @phpstan-param T $fallback
6285
     */
6286
    public function setAndGet($key, $fallback = null)
6287
    {
6288
        $this->generatorToArray();
77 ✔
6289

6290
        // If the key doesn't exist, set it.
6291
        if (!$this->has($key)) {
77 ✔
6292
            $this->array = $this->set($key, $fallback)->toArray();
28 ✔
6293
        }
6294

6295
        return $this->get($key);
77 ✔
6296
    }
6297

6298
    /**
6299
     * Shifts a specified value off the beginning of array.
6300
     *
6301
     * @return mixed|null
6302
     *                    <p>(Mutable) A shifted element from the current array.</p>
6303
     *
6304
     * @phpstan-return T|null
6305
     */
6306
    public function shift()
6307
    {
6308
        $this->generatorToArray();
35 ✔
6309

6310
        return \array_shift($this->array);
35 ✔
6311
    }
6312

6313
    /**
6314
     * Shuffle the current array.
6315
     *
6316
     * EXAMPLE: <code>
6317
     * a([1 => 'bar', 'foo' => 'foo'])->shuffle(); // e.g.: Arrayy[['foo' => 'foo', 1 => 'bar']]
6318
     * </code>
6319
     *
6320
     * @param bool       $secure <p>using a CSPRNG | @see https://paragonie.com/b/JvICXzh_jhLyt4y3</p>
6321
     * @param array|null $array  [optional]
6322
     *
6323
     * @return static
6324
     *                <p>(Immutable)</p>
6325
     *
6326
     * @phpstan-param  array<TKey,T> $array
6327
     * @phpstan-return static
6328
     */
6329
    public function shuffle(bool $secure = false, ?array $array = null): self
6330
    {
6331
        if ($array === null) {
14 ✔
6332
            $array = $this->toArray(false);
14 ✔
6333
        }
6334

6335
        if ($secure !== true) {
14 ✔
6336
            \shuffle($array);
14 ✔
6337
        } else {
6338
            $size = \count($array, \COUNT_NORMAL);
7 ✔
6339
            $keys = \array_keys($array);
7 ✔
6340
            for ($i = $size - 1; $i > 0; --$i) {
7 ✔
6341
                try {
6342
                    $r = \random_int(0, $i);
7 ✔
6343
                } catch (\Exception $e) {
×
6344
                    /** @noinspection RandomApiMigrationInspection - "random_int" is already in use */
6345
                    $r = \mt_rand(0, $i);
×
6346
                }
6347
                if ($r !== $i) {
7 ✔
6348
                    $temp = $array[$keys[$r]];
6 ✔
6349
                    $array[$keys[$r]] = $array[$keys[$i]];
6 ✔
6350
                    $array[$keys[$i]] = $temp;
6 ✔
6351
                }
6352
            }
6353
        }
6354

6355
        foreach ($array as $key => $value) {
14 ✔
6356
            // check if recursive is needed
6357
            if (\is_array($value)) {
14 ✔
6358
                /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
6359
                /** @phpstan-var array<TKey,T> $value */
6360
                $value = $value;
×
6361

6362
                $array[$key] = $this->shuffle($secure, $value);
×
6363
            }
6364
        }
6365

6366
        return static::create(
14 ✔
6367
            $array,
14 ✔
6368
            $this->iteratorClass,
14 ✔
6369
            false
14 ✔
6370
        );
14 ✔
6371
    }
6372

6373
    /**
6374
     * Count the values from the current array.
6375
     *
6376
     * alias: for "Arrayy->count()"
6377
     *
6378
     * @param int $mode
6379
     *
6380
     * @return int
6381
     */
6382
    public function size(int $mode = \COUNT_NORMAL): int
6383
    {
6384
        return $this->count($mode);
140 ✔
6385
    }
6386

6387
    /**
6388
     * Checks whether array has exactly $size items.
6389
     *
6390
     * @param int $size
6391
     *
6392
     * @return bool
6393
     */
6394
    public function sizeIs(int $size): bool
6395
    {
6396
        // init
6397
        $itemsTempCount = 0;
7 ✔
6398

6399
        /** @noinspection PhpUnusedLocalVariableInspection */
6400
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
6401
        foreach ($this->getGeneratorByReference() as &$value) {
7 ✔
6402
            ++$itemsTempCount;
7 ✔
6403
            if ($itemsTempCount > $size) {
7 ✔
6404
                return false;
7 ✔
6405
            }
6406
        }
6407

6408
        return $itemsTempCount === $size;
7 ✔
6409
    }
6410

6411
    /**
6412
     * Checks whether array has between $fromSize to $toSize items. $toSize can be
6413
     * smaller than $fromSize.
6414
     *
6415
     * @param int $fromSize
6416
     * @param int $toSize
6417
     *
6418
     * @return bool
6419
     */
6420
    public function sizeIsBetween(int $fromSize, int $toSize): bool
6421
    {
6422
        if ($fromSize > $toSize) {
7 ✔
6423
            $tmp = $toSize;
7 ✔
6424
            $toSize = $fromSize;
7 ✔
6425
            $fromSize = $tmp;
7 ✔
6426
        }
6427

6428
        // init
6429
        $itemsTempCount = 0;
7 ✔
6430

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

6439
        return $fromSize < $itemsTempCount && $itemsTempCount < $toSize;
7 ✔
6440
    }
6441

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

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

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

6465
    /**
6466
     * Checks whether array has less than $size items.
6467
     *
6468
     * @param int $size
6469
     *
6470
     * @return bool
6471
     */
6472
    public function sizeIsLessThan(int $size): bool
6473
    {
6474
        // init
6475
        $itemsTempCount = 0;
7 ✔
6476

6477
        /** @noinspection PhpUnusedLocalVariableInspection */
6478
        foreach ($this->getGenerator() as $value) {
7 ✔
6479
            ++$itemsTempCount;
7 ✔
6480
            if ($itemsTempCount > $size) {
7 ✔
6481
                return false;
7 ✔
6482
            }
6483
        }
6484

6485
        return $itemsTempCount < $size;
7 ✔
6486
    }
6487

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

6522
    /**
6523
     * Extract a slice of the array.
6524
     *
6525
     * @param int      $offset       <p>Slice begin index.</p>
6526
     * @param int|null $length       <p>Length of the slice.</p>
6527
     * @param bool     $preserveKeys <p>Whether array keys are preserved or no.</p>
6528
     *
6529
     * @return static
6530
     *                <p>(Immutable) A slice of the original array with length $length.</p>
6531
     *
6532
     * @phpstan-return static
6533
     * @psalm-mutation-free
6534
     */
6535
    public function slice(int $offset, ?int $length = null, bool $preserveKeys = false)
6536
    {
6537
        return static::create(
35 ✔
6538
            \array_slice(
35 ✔
6539
                $this->toArray(),
35 ✔
6540
                $offset,
35 ✔
6541
                $length,
35 ✔
6542
                $preserveKeys
35 ✔
6543
            ),
35 ✔
6544
            $this->iteratorClass,
35 ✔
6545
            false
35 ✔
6546
        );
35 ✔
6547
    }
6548

6549
    /**
6550
     * Sort the current array and optional you can keep the keys.
6551
     *
6552
     * EXAMPLE: <code>
6553
     * a(3 => 'd', 2 => 'f', 0 => 'a')->sort(SORT_ASC, SORT_NATURAL, false); // Arrayy[0 => 'a', 1 => 'd', 2 => 'f']
6554
     * </code>
6555
     *
6556
     * @param int|string $direction
6557
     *                              <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6558
     * @param int        $strategy
6559
     *                              <p>sort_flags => use e.g.: <strong>SORT_REGULAR</strong> (default) or
6560
     *                              <strong>SORT_NATURAL</strong></p>
6561
     * @param bool       $keepKeys
6562
     *
6563
     * @return static
6564
     *                <p>(Mutable) Return this Arrayy object.</p>
6565
     *
6566
     * @phpstan-return static
6567
     */
6568
    public function sort(
6569
        $direction = \SORT_ASC,
6570
        int $strategy = \SORT_REGULAR,
6571
        bool $keepKeys = false
6572
    ): self {
6573
        $this->generatorToArray();
140 ✔
6574

6575
        return $this->sorting(
140 ✔
6576
            $this->array,
140 ✔
6577
            $direction,
140 ✔
6578
            $strategy,
140 ✔
6579
            $keepKeys
140 ✔
6580
        );
140 ✔
6581
    }
6582

6583
    /**
6584
     * Sort the current array and optional you can keep the keys.
6585
     *
6586
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6587
     * @param int        $strategy  <p>sort_flags => use e.g.: <strong>SORT_REGULAR</strong> (default) or
6588
     *                              <strong>SORT_NATURAL</strong></p>
6589
     * @param bool       $keepKeys
6590
     *
6591
     * @return static
6592
     *                <p>(Immutable) Return this Arrayy object.</p>
6593
     *
6594
     * @phpstan-return static
6595
     */
6596
    public function sortImmutable(
6597
        $direction = \SORT_ASC,
6598
        int $strategy = \SORT_REGULAR,
6599
        bool $keepKeys = false
6600
    ): self {
6601
        $that = clone $this;
84 ✔
6602

6603
        $that->generatorToArray();
84 ✔
6604

6605
        return $that->sorting(
84 ✔
6606
            $that->array,
84 ✔
6607
            $direction,
84 ✔
6608
            $strategy,
84 ✔
6609
            $keepKeys
84 ✔
6610
        );
84 ✔
6611
    }
6612

6613
    /**
6614
     * Sort the current array by key.
6615
     *
6616
     * EXAMPLE: <code>
6617
     * a([1 => 2, 0 => 1])->sortKeys(\SORT_ASC); // Arrayy[0 => 1, 1 => 2]
6618
     * </code>
6619
     *
6620
     * @see http://php.net/manual/en/function.ksort.php
6621
     * @see http://php.net/manual/en/function.krsort.php
6622
     *
6623
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6624
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6625
     *                              <strong>SORT_NATURAL</strong></p>
6626
     *
6627
     * @return $this
6628
     *               <p>(Mutable) Return this Arrayy object.</p>
6629
     *
6630
     * @phpstan-return static
6631
     */
6632
    public function sortKeys(
6633
        $direction = \SORT_ASC,
6634
        int $strategy = \SORT_REGULAR
6635
    ): self {
6636
        $this->generatorToArray();
126 ✔
6637

6638
        $this->sorterKeys($this->array, $direction, $strategy);
126 ✔
6639

6640
        return $this;
126 ✔
6641
    }
6642

6643
    /**
6644
     * Sort the current array by key.
6645
     *
6646
     * @see          http://php.net/manual/en/function.ksort.php
6647
     * @see          http://php.net/manual/en/function.krsort.php
6648
     *
6649
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6650
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6651
     *                              <strong>SORT_NATURAL</strong></p>
6652
     *
6653
     * @return $this
6654
     *               <p>(Immutable) Return this Arrayy object.</p>
6655
     *
6656
     * @phpstan-return static
6657
     * @psalm-mutation-free
6658
     */
6659
    public function sortKeysImmutable(
6660
        $direction = \SORT_ASC,
6661
        int $strategy = \SORT_REGULAR
6662
    ): self {
6663
        $that = clone $this;
56 ✔
6664

6665
        /**
6666
         * @psalm-suppress ImpureMethodCall - object is already cloned
6667
         */
6668
        $that->sortKeys($direction, $strategy);
56 ✔
6669

6670
        return $that;
56 ✔
6671
    }
6672

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

6696
    /**
6697
     * Sort the current array by value.
6698
     *
6699
     * EXAMPLE: <code>
6700
     * a(3 => 'd', 2 => 'f', 0 => 'a')->sortValueNewIndex(SORT_ASC, SORT_NATURAL); // Arrayy[0 => 'a', 1 => 'd', 2 => 'f']
6701
     * </code>
6702
     *
6703
     * @param int|string $direction <p>use <strong>SORT_ASC</strong> (default) or <strong>SORT_DESC</strong></p>
6704
     * @param int        $strategy  <p>use e.g.: <strong>SORT_REGULAR</strong> (default) or
6705
     *                              <strong>SORT_NATURAL</strong></p>
6706
     *
6707
     * @return static
6708
     *                <p>(Mutable)</p>
6709
     *
6710
     * @phpstan-return static
6711
     */
6712
    public function sortValueNewIndex($direction = \SORT_ASC, int $strategy = \SORT_REGULAR): self
6713
    {
6714
        return $this->sort($direction, $strategy, false);
7 ✔
6715
    }
6716

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

6751
        // Transform all values into their results.
6752
        if ($sorter) {
7 ✔
6753
            $arrayy = static::create(
7 ✔
6754
                $array,
7 ✔
6755
                $this->iteratorClass,
7 ✔
6756
                false
7 ✔
6757
            );
7 ✔
6758

6759
            /**
6760
             * @psalm-suppress MissingClosureReturnType
6761
             * @psalm-suppress MissingClosureParamType
6762
             */
6763
            $results = $arrayy->each(
7 ✔
6764
                static function ($value) use ($sorter) {
7 ✔
6765
                    if (\is_callable($sorter) === true) {
7 ✔
6766
                        return $sorter($value);
7 ✔
6767
                    }
6768

6769
                    return $sorter === $value;
7 ✔
6770
                }
7 ✔
6771
            );
7 ✔
6772

6773
            $results = $results->toArray();
7 ✔
6774
        } else {
6775
            $results = $array;
7 ✔
6776
        }
6777

6778
        // Sort by the results and replace by original values
6779
        \array_multisort($results, $direction, $strategy, $array);
7 ✔
6780

6781
        return static::create(
7 ✔
6782
            $array,
7 ✔
6783
            $this->iteratorClass,
7 ✔
6784
            false
7 ✔
6785
        );
7 ✔
6786
    }
6787

6788
    /**
6789
     * @param int      $offset
6790
     * @param int|null $length
6791
     * @param array    $replacement
6792
     *
6793
     * @return static
6794
     *                <p>(Immutable)</p>
6795
     *
6796
     * @phpstan-param  array<T> $replacement
6797
     * @phpstan-return static
6798
     * @psalm-mutation-free
6799
     */
6800
    public function splice(int $offset, ?int $length = null, $replacement = []): self
6801
    {
6802
        $tmpArray = $this->toArray();
7 ✔
6803

6804
        \array_splice(
7 ✔
6805
            $tmpArray,
7 ✔
6806
            $offset,
7 ✔
6807
            $length ?? $this->count(),
7 ✔
6808
            $replacement
7 ✔
6809
        );
7 ✔
6810

6811
        return static::create(
7 ✔
6812
            $tmpArray,
7 ✔
6813
            $this->iteratorClass,
7 ✔
6814
            false
7 ✔
6815
        );
7 ✔
6816
    }
6817

6818
    /**
6819
     * Split an array in the given amount of pieces.
6820
     *
6821
     * EXAMPLE: <code>
6822
     * a(['a' => 1, 'b' => 2])->split(2, true); // Arrayy[['a' => 1], ['b' => 2]]
6823
     * </code>
6824
     *
6825
     * @param int  $numberOfPieces
6826
     * @param bool $keepKeys
6827
     *
6828
     * @return static
6829
     *                <p>(Immutable)</p>
6830
     *
6831
     * @phpstan-return static
6832
     * @psalm-mutation-free
6833
     */
6834
    public function split(int $numberOfPieces = 2, bool $keepKeys = false): self
6835
    {
6836
        if ($keepKeys) {
7 ✔
6837
            $generator = function () use ($numberOfPieces) {
7 ✔
6838
                $carry = [];
7 ✔
6839
                $i = 1;
7 ✔
6840
                foreach ($this->getGenerator() as $key => $value) {
7 ✔
6841
                    $carry[$key] = $value;
7 ✔
6842

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

6846
                        continue;
7 ✔
6847
                    }
6848

6849
                    yield $carry;
7 ✔
6850

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

6855
                if ($carry !== []) {
7 ✔
6856
                    yield $carry;
7 ✔
6857
                }
6858
            };
7 ✔
6859
        } else {
6860
            $generator = function () use ($numberOfPieces) {
7 ✔
6861
                $carry = [];
7 ✔
6862
                $i = 1;
7 ✔
6863
                foreach ($this->getGenerator() as $value) {
7 ✔
6864
                    $carry[] = $value;
7 ✔
6865

6866
                    if ($i % $numberOfPieces !== 0) {
7 ✔
6867
                        ++$i;
7 ✔
6868

6869
                        continue;
7 ✔
6870
                    }
6871

6872
                    yield $carry;
7 ✔
6873

6874
                    $carry = [];
7 ✔
6875
                    $i = 1;
7 ✔
6876
                }
6877

6878
                if ($carry !== []) {
7 ✔
6879
                    yield $carry;
7 ✔
6880
                }
6881
            };
7 ✔
6882
        }
6883

6884
        return static::create(
7 ✔
6885
            $generator,
7 ✔
6886
            $this->iteratorClass,
7 ✔
6887
            false
7 ✔
6888
        );
7 ✔
6889
    }
6890

6891
    /**
6892
     * Strip all empty items from the current array.
6893
     *
6894
     * EXAMPLE: <code>
6895
     * a(['a' => 1, 'b' => ''])->stripEmpty(); // Arrayy[['a' => 1]]
6896
     * </code>
6897
     *
6898
     * @return static
6899
     *                <p>(Immutable)</p>
6900
     *
6901
     * @phpstan-return static
6902
     * @psalm-mutation-free
6903
     */
6904
    public function stripEmpty(): self
6905
    {
6906
        return $this->filter(
7 ✔
6907
            static function ($item) {
7 ✔
6908
                if ($item === null) {
7 ✔
6909
                    return false;
7 ✔
6910
                }
6911

6912
                return (bool) \trim((string) $item);
7 ✔
6913
            }
7 ✔
6914
        );
7 ✔
6915
    }
6916

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

6937
        list($array[$swapA], $array[$swapB]) = [$array[$swapB], $array[$swapA]];
7 ✔
6938

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

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

6971
            foreach ($this->getGenerator() as $key => $value) {
21 ✔
6972
                if ($value instanceof self) {
21 ✔
6973
                    $value = $value->toArray(
14 ✔
6974
                        $convertAllArrayyElements,
14 ✔
6975
                        $preserveKeys
14 ✔
6976
                    );
14 ✔
6977
                }
6978

6979
                if ($preserveKeys) {
21 ✔
6980
                    $array[$key] = $value;
14 ✔
6981
                } else {
6982
                    $array[] = $value;
7 ✔
6983
                }
6984
            }
6985

6986
            /* @phpstan-ignore-next-line - depends on the $convertAllArrayyElements parameter :/ */
6987
            return $array;
21 ✔
6988
        }
6989

6990
        return \iterator_to_array($this->getGenerator(), $preserveKeys);
6,734 ✔
6991
    }
6992

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

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

7032
        $return = \json_encode($this->toArray(), $options, $depth);
91 ✔
7033
        if ($return === false) {
91 ✔
7034
            return '';
×
7035
        }
7036

7037
        return $return;
91 ✔
7038
    }
7039

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

7053
        if ($items === null) {
7 ✔
7054
            $items = $this->toArray();
7 ✔
7055
        }
7056

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

7075
        /** @var static $return  - help for phpstan */
7076
        $return = static::create(
7 ✔
7077
            $return,
7 ✔
7078
            $this->iteratorClass,
7 ✔
7079
            false
7 ✔
7080
        );
7 ✔
7081

7082
        return $return;
7 ✔
7083
    }
7084

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

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

7114
        $this->array = $this->reduce(
91 ✔
7115
            static function ($resultArray, $value, $key) {
91 ✔
7116
                if (!\in_array($value, $resultArray, true)) {
84 ✔
7117
                    $resultArray[] = $value;
84 ✔
7118
                }
7119

7120
                return $resultArray;
84 ✔
7121
            },
91 ✔
7122
            []
91 ✔
7123
        )->toArray();
91 ✔
7124
        $this->generator = null;
91 ✔
7125

7126
        return $this;
91 ✔
7127
    }
7128

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

7145
        // init
7146
        $array = $this->toArray();
77 ✔
7147

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

7159
                return $resultArray;
70 ✔
7160
            },
77 ✔
7161
            []
77 ✔
7162
        );
77 ✔
7163
        $this->generator = null;
77 ✔
7164

7165
        return $this;
77 ✔
7166
    }
7167

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

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

7198
        if (
7199
            $this->checkPropertyTypes
42 ✔
7200
            &&
7201
            $this->properties !== []
42 ✔
7202
        ) {
7203
            foreach ($args as $key => $value) {
14 ✔
7204
                $this->checkType($key, $value);
14 ✔
7205
            }
7206
        }
7207

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

7210
        return $this;
35 ✔
7211
    }
7212

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

7231
        return true;
7 ✔
7232
    }
7233

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

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

7298
        if ($this->array !== []) {
84 ✔
7299
            if ($recursive === true) {
70 ✔
7300
                if ($userData !== self::ARRAYY_HELPER_WALK) {
35 ✔
7301
                    \array_walk_recursive($this->array, $callable, $userData);
×
7302
                } else {
7303
                    \array_walk_recursive($this->array, $callable);
35 ✔
7304
                }
7305
            } else {
7306
                if ($userData !== self::ARRAYY_HELPER_WALK) {
35 ✔
7307
                    \array_walk($this->array, $callable, $userData);
×
7308
                } else {
7309
                    /* @phpstan-ignore-next-line | callback with no arguments is ok here */
7310
                    \array_walk($this->array, $callable);
35 ✔
7311
                }
7312
            }
7313
        }
7314

7315
        return $this;
84 ✔
7316
    }
7317

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

7341
                return $accessorValue === $value;
×
7342
            }
7 ✔
7343
        );
7 ✔
7344
    }
7345

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

7360
        if (\count($array, \COUNT_NORMAL) <= 0) {
28 ✔
7361
            return $object;
7 ✔
7362
        }
7363

7364
        foreach ($array as $name => $value) {
21 ✔
7365
            if (\is_array($value)) {
21 ✔
7366
                $object->{$name} = static::arrayToObject($value);
7 ✔
7367
            } else {
7368
                $object->{$name} = $value;
21 ✔
7369
            }
7370
        }
7371

7372
        return $object;
21 ✔
7373
    }
7374

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

7407
        if ($input === null) {
77 ✔
7408
            $input = $this->getGenerator();
28 ✔
7409
        }
7410

7411
        if ($search_values === null) {
77 ✔
7412
            foreach ($input as $key => $value) {
77 ✔
7413
                $keys[] = $key;
77 ✔
7414

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

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

7450
                // check if recursive is needed
7451
                if (\is_array($value)) {
7 ✔
7452
                    $keysTmp[] = $this->array_keys_recursive($value);
7 ✔
7453
                }
7454
            }
7455
        }
7456

7457
        return $keysTmp === [] ? $keys : \array_merge($keys, ...$keysTmp);
77 ✔
7458
    }
7459

7460
    /**
7461
     * @param string     $path
7462
     * @param callable   $callable
7463
     * @param array|null $currentOffset
7464
     *
7465
     * @return void
7466
     *
7467
     * @phpstan-param array<TKey,T>|null $currentOffset
7468
     * @psalm-mutation-free
7469
     */
7470
    protected function callAtPath($path, $callable, &$currentOffset = null)
7471
    {
7472
        $this->generatorToArray();
70 ✔
7473

7474
        if ($currentOffset === null) {
70 ✔
7475
            $currentOffset = &$this->array;
70 ✔
7476
        }
7477

7478
        $explodedPath = \explode($this->pathSeparator, $path);
70 ✔
7479
        /* @phpstan-ignore-next-line | change from PHP >= 8, but we still support old versions */
7480
        if ($explodedPath === false) {
70 ✔
7481
            return;
×
7482
        }
7483

7484
        $nextPath = \array_shift($explodedPath);
70 ✔
7485
        if (!isset($currentOffset[$nextPath])) {
70 ✔
7486
            return;
7 ✔
7487
        }
7488

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

7500
    /**
7501
     * Extracts the value of the given property or method from the object.
7502
     *
7503
     * @param static $object
7504
     *                                         <p>The object to extract the value from.</p>
7505
     * @param string    $keyOrPropertyOrMethod
7506
     *                                         <p>The property or method for which the
7507
     *                                         value should be extracted.</p>
7508
     *
7509
     * @throws \InvalidArgumentException if the method or property is not defined
7510
     *
7511
     * @return mixed
7512
     *               <p>The value extracted from the specified property or method.</p>
7513
     *
7514
     * @phpstan-param self<TKey,T,TData> $object
7515
     */
7516
    final protected function extractValue(self $object, string $keyOrPropertyOrMethod)
7517
    {
7518
        if (isset($object[$keyOrPropertyOrMethod])) {
7 ✔
7519
            $return = $object->get($keyOrPropertyOrMethod);
7 ✔
7520

7521
            if ($return instanceof self) {
7 ✔
7522
                return $return->toArray();
×
7523
            }
7524

7525
            return $return;
7 ✔
7526
        }
7527

7528
        if (\property_exists($object, $keyOrPropertyOrMethod)) {
×
7529
            return $object->{$keyOrPropertyOrMethod};
×
7530
        }
7531

7532
        if (\method_exists($object, $keyOrPropertyOrMethod)) {
×
7533
            return $object->{$keyOrPropertyOrMethod}();
×
7534
        }
7535

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

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

7562
        if ($data === null) {
9,119 ✔
7563
            throw new \InvalidArgumentException('Passed value should be a array');
14 ✔
7564
        }
7565

7566
        return $data;
9,105 ✔
7567
    }
7568

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

7586
            return true;
21 ✔
7587
        }
7588

7589
        return false;
8,419 ✔
7590
    }
7591

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

7605
            if ($direction === 'desc') {
70 ✔
7606
                $direction = \SORT_DESC;
14 ✔
7607
            } else {
7608
                $direction = \SORT_ASC;
63 ✔
7609
            }
7610
        }
7611

7612
        if (
7613
            $direction !== \SORT_DESC
301 ✔
7614
            &&
7615
            $direction !== \SORT_ASC
301 ✔
7616
        ) {
7617
            $direction = \SORT_ASC;
×
7618
        }
7619

7620
        return $direction;
301 ✔
7621
    }
7622

7623
    /**
7624
     * @return TypeCheckInterface[]
7625
     *
7626
     * @noinspection ReturnTypeCanBeDeclaredInspection
7627
     */
7628
    protected function getPropertiesFromPhpDoc()
7629
    {
7630
        static $PROPERTY_CACHE = [];
502 ✔
7631
        static $OPTIONAL_PROPERTY_CACHE = [];
502 ✔
7632
        $cacheKey = 'Class::' . static::class;
502 ✔
7633

7634
        if (isset($PROPERTY_CACHE[$cacheKey])) {
502 ✔
7635
            $this->optionalProperties = $OPTIONAL_PROPERTY_CACHE[$cacheKey] ?? [];
446 ✔
7636

7637
            return $PROPERTY_CACHE[$cacheKey];
446 ✔
7638
        }
7639

7640
        $properties = $this->getPropertiesFromNativeDefinitions();
138 ✔
7641
        $optionalProperties = [];
138 ✔
7642
        $phpDocPropertyAnnotationStyle = null;
138 ✔
7643

7644
        $reflector = new \ReflectionClass($this);
138 ✔
7645
        $factory = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
138 ✔
7646
        $docComment = $reflector->getDocComment();
138 ✔
7647
        if ($docComment) {
138 ✔
7648
            $docblock = $factory->create($docComment);
131 ✔
7649
            $this->addPropertiesFromDocBlock($docblock, $properties, $optionalProperties, $phpDocPropertyAnnotationStyle);
131 ✔
7650
        }
7651

7652
        /** @noinspection PhpAssignmentInConditionInspection */
7653
        while ($reflector = $reflector->getParentClass()) {
131 ✔
7654
            $docComment = $reflector->getDocComment();
131 ✔
7655
            if ($docComment) {
131 ✔
7656
                $docblock = $factory->create($docComment);
131 ✔
7657
                $this->addPropertiesFromDocBlock($docblock, $properties, $optionalProperties, $phpDocPropertyAnnotationStyle);
131 ✔
7658
            }
7659
        }
7660

7661
        $this->optionalProperties = $optionalProperties;
124 ✔
7662
        $OPTIONAL_PROPERTY_CACHE[$cacheKey] = $optionalProperties;
124 ✔
7663

7664
        return $PROPERTY_CACHE[$cacheKey] = $properties;
124 ✔
7665
    }
7666

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

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

7686
        $currentPhpDocPropertyAnnotationStyle = null;
131 ✔
7687
        if ($propertyTags !== []) {
131 ✔
7688
            $currentPhpDocPropertyAnnotationStyle = 'property';
49 ✔
7689
        } elseif ($arrayShapeItems !== []) {
131 ✔
7690
            $currentPhpDocPropertyAnnotationStyle = 'array-shape';
49 ✔
7691
        }
7692

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

7703
        if ($currentPhpDocPropertyAnnotationStyle !== null) {
131 ✔
7704
            $phpDocPropertyAnnotationStyle = $currentPhpDocPropertyAnnotationStyle;
91 ✔
7705
        }
7706

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

7724
        foreach ($arrayShapeItems as $item) {
131 ✔
7725
            $typeName = (string) $item->getKey();
49 ✔
7726
            if ($typeName === '') {
49 ✔
NEW
7727
                continue;
×
7728
            }
7729

7730
            $typeName = \trim($typeName, '\'"');
49 ✔
7731
            if (isset($properties[$typeName])) {
49 ✔
NEW
7732
                continue;
×
7733
            }
7734

7735
            $typeCheckPhpDoc = TypeCheckPhpDoc::fromDocTypeObject($typeName, $item->getValue());
49 ✔
7736
            $properties[$typeName] = $typeCheckPhpDoc;
49 ✔
7737
            if ($item->isOptional()) {
49 ✔
7738
                $optionalProperties[$typeName] = true;
28 ✔
7739
            }
7740
        }
7741
    }
7742

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

7756
        $items = [];
138 ✔
7757
        foreach ($docblock->getTagsByName('template') as $tag) {
138 ✔
7758
            if (
7759
                $tag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Template
138 ✔
7760
                &&
7761
                $tag->getTemplateName() === 'T'
138 ✔
7762
                &&
7763
                $tag->getBound() instanceof \phpDocumentor\Reflection\PseudoTypes\ArrayShape
138 ✔
7764
            ) {
7765
                foreach ($tag->getBound()->getItems() as $item) {
42 ✔
7766
                    $items[] = $item;
42 ✔
7767
                }
7768
            }
7769
        }
7770

7771
        foreach ($docblock->getTagsByName('extends') as $tag) {
138 ✔
7772
            if (!$tag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Extends_) {
138 ✔
NEW
7773
                continue;
×
7774
            }
7775

7776
            $type = $tag->getType();
138 ✔
7777
            if (
7778
                !$type instanceof \phpDocumentor\Reflection\PseudoTypes\Generic
138 ✔
7779
                ||
7780
                !$this->isArrayyGenericTarget((string) $type->getFqsen())
138 ✔
7781
            ) {
7782
                continue;
124 ✔
7783
            }
7784

7785
            foreach ($type->getTypes() as $genericType) {
131 ✔
7786
                if ($genericType instanceof \phpDocumentor\Reflection\PseudoTypes\ArrayShape) {
131 ✔
7787
                    foreach ($genericType->getItems() as $item) {
14 ✔
7788
                        $items[] = $item;
14 ✔
7789
                    }
7790
                }
7791
            }
7792
        }
7793

7794
        return $items;
138 ✔
7795
    }
7796

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

7811
        if (\in_array($fqcn, [self::class, ArrayyStrict::class], true)) {
138 ✔
7812
            return true;
131 ✔
7813
        }
7814

7815
        return \class_exists($fqcn) && \is_a($fqcn, self::class, true);
124 ✔
7816
    }
7817

7818
    /**
7819
     * @return TypeCheckInterface[]
7820
     */
7821
    protected function getPropertiesFromNativeDefinitions(): array
7822
    {
7823
        $properties = [];
138 ✔
7824
        $reflector = new \ReflectionClass($this);
138 ✔
7825
        $reservedProperties = self::getReservedPropertyNames();
138 ✔
7826

7827
        do {
7828
            if ($reflector->getName() === self::class) {
138 ✔
7829
                break;
138 ✔
7830
            }
7831

7832
            foreach ($reflector->getProperties() as $property) {
138 ✔
7833
                if (
7834
                    $property->getDeclaringClass()->getName() !== $reflector->getName()
138 ✔
7835
                    ||
7836
                    $property->isStatic()
117 ✔
7837
                    ||
7838
                    isset($reservedProperties[$property->getName()])
117 ✔
7839
                    ||
7840
                    isset($properties[$property->getName()])
138 ✔
7841
                ) {
7842
                    continue;
138 ✔
7843
                }
7844

7845
                $properties[$property->getName()] = TypeCheckPhpDoc::fromReflectionProperty($property);
19 ✔
7846
            }
7847
        } while ($reflector = $reflector->getParentClass());
138 ✔
7848

7849
        return $properties;
138 ✔
7850
    }
7851

7852
    /**
7853
     * @return array<string, true>
7854
     */
7855
    private static function getReservedPropertyNames(): array
7856
    {
7857
        static $reservedProperties = null;
138 ✔
7858

7859
        if ($reservedProperties !== null) {
138 ✔
7860
            return $reservedProperties;
131 ✔
7861
        }
7862

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

7870
            $reservedProperties[$property->getName()] = true;
7 ✔
7871
        }
7872

7873
        return $reservedProperties;
7 ✔
7874
    }
7875

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

7895
        if (\is_array($pieces)) {
259 ✔
7896
            /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
7897
            /** @phpstan-var array<TKey,T> $pieces */
7898
            $pieces = $pieces;
259 ✔
7899

7900
            $pieces_count = \count($pieces, \COUNT_NORMAL);
259 ✔
7901
            $pieces_count_not_zero = $pieces_count > 0;
259 ✔
7902

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

7913
        if (
7914
            \is_scalar($pieces) === true
259 ✔
7915
            ||
7916
            (\is_object($pieces) && \method_exists($pieces, '__toString'))
259 ✔
7917
        ) {
7918
            return (string) $pieces;
231 ✔
7919
        }
7920

7921
        return '';
56 ✔
7922
    }
7923

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

7955
        foreach ($haystack as $item) {
127 ✔
7956
            if (\is_array($item)) {
99 ✔
7957
                $returnTmp = $this->in_array_recursive($needle, $item, $strict);
22 ✔
7958
            } else {
7959
                /** @noinspection NestedPositiveIfStatementsInspection */
7960
                if ($strict === true) {
99 ✔
7961
                    $returnTmp = $item === $needle;
99 ✔
7962
                } else {
7963
                    $returnTmp = $item == $needle;
×
7964
                }
7965
            }
7966

7967
            if ($returnTmp === true) {
99 ✔
7968
                return true;
71 ✔
7969
            }
7970
        }
7971

7972
        return false;
56 ✔
7973
    }
7974

7975
    /**
7976
     * @param mixed $data
7977
     *
7978
     * @return array<mixed>|null
7979
     */
7980
    protected function internalGetArray(&$data)
7981
    {
7982
        if (\is_array($data)) {
9,119 ✔
7983
            return $data;
9,077 ✔
7984
        }
7985

7986
        if (!$data) {
791 ✔
7987
            return [];
49 ✔
7988
        }
7989

7990
        if (\is_object($data) === true) {
784 ✔
7991
            if ($data instanceof \ArrayObject) {
735 ✔
7992
                return $data->getArrayCopy();
35 ✔
7993
            }
7994

7995
            if ($data instanceof \Generator) {
707 ✔
7996
                return static::createFromGeneratorImmutable($data)->toArray();
×
7997
            }
7998

7999
            if ($data instanceof \Traversable) {
707 ✔
8000
                return static::createFromObject($data)->toArray();
×
8001
            }
8002

8003
            if ($data instanceof \JsonSerializable) {
707 ✔
8004
                return (array) $data->jsonSerialize();
×
8005
            }
8006

8007
            if (\method_exists($data, '__toArray')) {
707 ✔
8008
                return (array) $data->__toArray();
×
8009
            }
8010

8011
            if (\method_exists($data, '__toString')) {
707 ✔
8012
                return [(string) $data];
×
8013
            }
8014
        }
8015

8016
        if (\is_callable($data)) {
756 ✔
8017
            /**
8018
             * @psalm-suppress InvalidPropertyAssignmentValue - why?
8019
             */
8020
            $this->generator = new ArrayyRewindableGenerator($data);
693 ✔
8021

8022
            return [];
693 ✔
8023
        }
8024

8025
        if (\is_scalar($data)) {
77 ✔
8026
            return [$data];
63 ✔
8027
        }
8028

8029
        return null;
14 ✔
8030
    }
8031

8032
    /**
8033
     * Internal mechanics of remove method.
8034
     *
8035
     * @param float|int|string $key
8036
     *
8037
     * @return bool
8038
     */
8039
    protected function internalRemove($key): bool
8040
    {
8041
        $this->generatorToArray();
154 ✔
8042

8043
        if (
8044
            $this->pathSeparator
154 ✔
8045
            &&
8046
            (string) $key === $key
154 ✔
8047
            &&
8048
            \strpos($key, $this->pathSeparator) !== false
154 ✔
8049
        ) {
8050
            $path = \explode($this->pathSeparator, (string) $key);
×
8051

8052
            if ($path !== false) {
×
8053
                // crawl though the keys
8054
                while (\count($path, \COUNT_NORMAL) > 1) {
×
8055
                    $key = \array_shift($path);
×
8056

8057
                    if (!$this->has($key)) {
×
8058
                        return false;
×
8059
                    }
8060

8061
                    $this->array = &$this->array[$key];
×
8062
                }
8063

8064
                $key = \array_shift($path);
×
8065
            }
8066
        }
8067

8068
        unset($this->array[$key]);
154 ✔
8069

8070
        return true;
154 ✔
8071
    }
8072

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

8098
        if ($key === null) {
7,978 ✔
8099
            return false;
×
8100
        }
8101

8102
        $this->generatorToArray();
7,978 ✔
8103

8104
        $array = &$this->array;
7,978 ✔
8105

8106
        /**
8107
         * https://github.com/vimeo/psalm/issues/2536
8108
         *
8109
         * @psalm-suppress PossiblyInvalidArgument
8110
         * @psalm-suppress InvalidScalarArgument
8111
         */
8112
        if (
8113
            $this->pathSeparator
7,978 ✔
8114
            &&
8115
            (string) $key === $key
7,978 ✔
8116
            &&
8117
            \strpos($key, $this->pathSeparator) !== false
7,978 ✔
8118
        ) {
8119
            $path = \explode($this->pathSeparator, (string) $key);
63 ✔
8120

8121
            if ($path !== false) {
63 ✔
8122
                // crawl through the keys
8123
                while (\count($path, \COUNT_NORMAL) > 1) {
63 ✔
8124
                    $key = \array_shift($path);
63 ✔
8125

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

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

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

8139
        $array[$key] = $value;
7,978 ✔
8140

8141
        return true;
7,978 ✔
8142
    }
8143

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

8159
        $object = \get_object_vars($object);
42 ✔
8160

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

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

8181
        if ($this->properties === []) {
9,105 ✔
8182
            if (
8183
                $this->checkPropertyTypes === true
8,412 ✔
8184
                ||
8185
                $checkPropertiesInConstructor === true
8,412 ✔
8186
            ) {
8187
                $this->properties = $this->getPropertiesFromPhpDoc();
481 ✔
8188
            }
8189

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

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

8205
        foreach ($data as $key => &$valueInner) {
9,077 ✔
8206
            $this->internalSet(
7,894 ✔
8207
                $key,
7,894 ✔
8208
                $valueInner,
7,894 ✔
8209
                $checkPropertiesInConstructor
7,894 ✔
8210
            );
7,894 ✔
8211
        }
8212
    }
8213

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

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

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

8247
        return $this;
126 ✔
8248
    }
8249

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

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

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

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

8295
        return $this;
168 ✔
8296
    }
8297

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

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

8325
        return $array;
175 ✔
8326
    }
8327

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

8346
        if (isset($this->properties[self::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES])) {
1,183 ✔
8347
            $this->properties[self::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES]->checkType($value);
833 ✔
8348
        } elseif ($key !== null && isset($this->properties[$key])) {
427 ✔
8349
            $this->properties[$key]->checkType($value);
427 ✔
8350
        }
8351
    }
8352
}
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