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

voku / Arrayy / 36286192881

27 Sep 2026 01:39AM UTC coverage: 92.359% (-0.02%) from 92.38%
36286192881

Pull #168

github

web-flow
Merge 728e86f65 into 17d12ec25
Pull Request #168: Integrate Infection PHPStan checks into CI without a generated PHPStan baseline

14 of 17 new or added lines in 3 files covered. (82.35%)

2768 of 2997 relevant lines covered (92.36%)

213.54 hits per line

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

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

3
declare(strict_types=1);
4

5
namespace Arrayy;
6

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

136
        $this->setInitialValuesAndProperties($data, $checkPropertiesInConstructor);
7,816✔
137

138
        $this->setIteratorClass($iteratorClass);
7,654✔
139
    }
140

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

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

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

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

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

176
        return $return;
×
177
    }
178

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

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

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

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

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

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

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

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

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

288
            return $this;
24✔
289
        }
290

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

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

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

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

334
        return $this;
120✔
335
    }
336

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

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

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

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

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

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

404
        return $this;
24✔
405
    }
406

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

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

431
        return $that;
24✔
432
    }
433

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

574
        return $return;
×
575
    }
576

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

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

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

610
        return $this;
24✔
611
    }
612

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

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

636
        return $that;
24✔
637
    }
638

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

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

654
        return $this;
48✔
655
    }
656

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

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

675
        return $that;
24✔
676
    }
677

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

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

693
        return $this;
60✔
694
    }
695

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

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

714
        return $that;
24✔
715
    }
716

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

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

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

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

770
        return $offsetExists;
834✔
771
    }
772

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

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

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

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

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

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

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

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

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

848
            return;
84✔
849
        }
850

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

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

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

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

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

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

919
            return;
7,654✔
920
        }
921

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

931
                return;
×
932
            }
933
        }
934

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

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

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

958
        return $this;
48✔
959
    }
960

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

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

984
        return $that;
24✔
985
    }
986

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

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

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

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

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

1087
        return $this;
6✔
1088
    }
1089

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

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

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

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

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

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

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

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

1170
        return $this;
24✔
1171
    }
1172

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

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

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

1190
        return $that;
60✔
1191
    }
1192

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

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

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

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

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

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

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

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

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

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

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

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

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

1336
        return $this;
66✔
1337
    }
1338

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

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

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

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

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

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

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

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

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

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

1453
            return $this;
18✔
1454
        }
1455

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

1459
        return $this;
42✔
1460
    }
1461

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

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

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

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

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

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

1536
        return false;
42✔
1537
    }
1538

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

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

1573
            return false;
48✔
1574
        }
1575

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

1583
        return false;
24✔
1584
    }
1585

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

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

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

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

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

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

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

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

1747
        return $return;
42✔
1748
    }
1749

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

1777
        return $instance;
4,688✔
1778
    }
1779

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

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

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

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

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

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

1841
        return $this;
171✔
1842
    }
1843

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

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

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

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

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

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

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

1941
        return $arrayy;
24✔
1942
    }
1943

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

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

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

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

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

2007
        return $return;
60✔
2008
    }
2009

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

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

2048
        return $return;
12✔
2049
    }
2050

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

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

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

2097
        \uksort($this->array, $callable);
30✔
2098

2099
        return $this;
30✔
2100
    }
2101

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

2122
        $that->generatorToArray();
6✔
2123

2124
        /**
2125
         * @psalm-suppress ImpureFunctionCall - object is already cloned
2126
         */
2127
        \uksort($that->array, $callable);
6✔
2128

2129
        return $that;
6✔
2130
    }
2131

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

2160
        \usort($this->array, $callable);
66✔
2161

2162
        return $this;
66✔
2163
    }
2164

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

2185
        /**
2186
         * @psalm-suppress ImpureMethodCall - object is already cloned
2187
         */
2188
        $that->customSortValues($callable);
30✔
2189

2190
        return $that;
30✔
2191
    }
2192

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

2204
        foreach ($keyOrKeys as $key) {
54✔
2205
            $this->offsetUnset($key);
54✔
2206
        }
2207
    }
2208

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

2233
        $generator = function () use ($array): \Generator {
78✔
2234
            foreach ($this->getGenerator() as $key => $value) {
78✔
2235
                if (\in_array($value, $array, true) === false) {
66✔
2236
                    yield $key => $value;
30✔
2237
                }
2238
            }
2239
        };
78✔
2240

2241
        return static::create(
78✔
2242
            $generator,
78✔
2243
            $this->iteratorClass,
78✔
2244
            false
78✔
2245
        );
78✔
2246
    }
2247

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

2268
        $generator = function () use ($array): \Generator {
54✔
2269
            foreach ($this->getGenerator() as $key => $value) {
54✔
2270
                if (\array_key_exists($key, $array) === false) {
48✔
2271
                    yield $key => $value;
12✔
2272
                }
2273
            }
2274
        };
54✔
2275

2276
        return static::create(
54✔
2277
            $generator,
54✔
2278
            $this->iteratorClass,
54✔
2279
            false
54✔
2280
        );
54✔
2281
    }
2282

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

2303
        $generator = function () use ($array): \Generator {
54✔
2304
            foreach ($this->getGenerator() as $key => $value) {
54✔
2305
                $isset = isset($array[$key]);
48✔
2306

2307
                if (
2308
                    !$isset
48✔
2309
                    ||
2310
                    $array[$key] !== $value
48✔
2311
                ) {
2312
                    yield $key => $value;
24✔
2313
                }
2314
            }
2315
        };
54✔
2316

2317
        return static::create(
54✔
2318
            $generator,
54✔
2319
            $this->iteratorClass,
54✔
2320
            false
54✔
2321
        );
54✔
2322
    }
2323

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

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

2357
        foreach ($arrayForTheLoop as $key => $value) {
6✔
2358
            if ($value instanceof self) {
6✔
2359
                $value = $value->toArray();
6✔
2360
            }
2361

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

2371
        return static::create(
6✔
2372
            $result,
6✔
2373
            $this->iteratorClass,
6✔
2374
            false
6✔
2375
        );
6✔
2376
    }
2377

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

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

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

2453
        foreach ($this->getGenerator() as $key => $value) {
36✔
2454
            $array[$key] = $closure($value, $key);
36✔
2455
        }
2456

2457
        return static::create(
36✔
2458
            $array,
36✔
2459
            $this->iteratorClass,
36✔
2460
            false
36✔
2461
        );
36✔
2462
    }
2463

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

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

2488
        return \end($this->array);
×
2489
    }
2490

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

2513
        foreach ($this->getGenerator() as $key => $value) {
24✔
2514
            if ($closure($value, $key)) {
18✔
2515
                $isExists = true;
6✔
2516

2517
                break;
6✔
2518
            }
2519
        }
2520

2521
        return $isExists;
24✔
2522
    }
2523

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

2547
        $this->generatorToArray();
42✔
2548

2549
        $tmpArray = $this->array;
42✔
2550

2551
        $count = \count($tmpArray);
42✔
2552

2553
        while ($count < $num) {
42✔
2554
            $tmpArray[] = $default;
24✔
2555
            ++$count;
24✔
2556
        }
2557

2558
        return static::create(
42✔
2559
            $tmpArray,
42✔
2560
            $this->iteratorClass,
42✔
2561
            false
42✔
2562
        );
42✔
2563
    }
2564

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

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

2624
            $generator = function () use ($closure) {
72✔
2625
                foreach ($this->getGenerator() as $key => $value) {
66✔
2626
                    if ($closure($value, $key) === true) {
60✔
2627
                        yield $key => $value;
54✔
2628
                    }
2629
                }
2630
            };
72✔
2631
        } else {
2632
            $generator = function () use ($closure) {
6✔
2633
                foreach ($this->getGenerator() as $key => $value) {
6✔
2634
                    if ($closure($value) === true) {
6✔
2635
                        yield $key => $value;
6✔
2636
                    }
2637
                }
2638
            };
6✔
2639
        }
2640

2641
        return static::create(
72✔
2642
            $generator,
72✔
2643
            $this->iteratorClass,
72✔
2644
            false
72✔
2645
        );
72✔
2646
    }
2647

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

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

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

2739
                    return $ops[$comparisonOp]($item, $property, $value);
6✔
2740
                }
6✔
2741
            )
6✔
2742
        );
6✔
2743

2744
        return static::create(
6✔
2745
            $result,
6✔
2746
            $this->iteratorClass,
6✔
2747
            false
6✔
2748
        );
6✔
2749
    }
2750

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

2778
        return false;
18✔
2779
    }
2780

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

2808
        return false;
24✔
2809
    }
2810

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

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

2858
        return $this->get($key_first);
117✔
2859
    }
2860

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

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

2878
        return $return;
177✔
2879
    }
2880

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

2901
        if ($number === null) {
222✔
2902
            $array = (array) \array_shift($arrayTmp);
84✔
2903
        } else {
2904
            $array = \array_splice($arrayTmp, 0, $number);
138✔
2905
        }
2906

2907
        return static::create(
222✔
2908
            $array,
222✔
2909
            $this->iteratorClass,
222✔
2910
            false
222✔
2911
        );
222✔
2912
    }
2913

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

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

2936
        return static::create(
18✔
2937
            $array,
18✔
2938
            $this->iteratorClass,
18✔
2939
            false
18✔
2940
        );
18✔
2941
    }
2942

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

2962
        if ($number === null) {
204✔
2963
            $shift = \array_shift($this->array);
114✔
2964
            /* @phpstan-ignore assign.propertyType */
2965
            $this->array = $shift !== null ? [$shift] : [];
114✔
2966
        } else {
2967
            $splice = \array_splice($this->array, 0, $number);
90✔
2968
            $this->array = $splice;
90✔
2969
        }
2970

2971
        return $this;
204✔
2972
    }
2973

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

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

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

3043
            return clone $this;
6✔
3044
        }
3045

3046
        if ($array !== null) {
1,702✔
3047
            if ($useByReference) {
24✔
3048
                $usedArray = &$array;
×
3049
            } else {
3050
                $usedArray = $array;
24✔
3051
            }
3052
        } else {
3053
            $this->generatorToArray();
1,684✔
3054

3055
            if ($useByReference) {
1,684✔
3056
                $usedArray = &$this->array;
1,006✔
3057
            } else {
3058
                $usedArray = $this->array;
789✔
3059
            }
3060
        }
3061

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

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

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

3085
            return $usedArray[$key];
1,378✔
3086
        }
3087

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

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

3113
                    continue;
180✔
3114
                }
3115

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

3123
                    continue;
6✔
3124
                }
3125

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

3139
                            continue;
×
3140
                        }
3141

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

3153
                            continue;
×
3154
                        }
3155

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

3163
                            continue;
6✔
3164
                        }
3165
                    }
3166

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

3172
                return $fallback instanceof \Closure ? $fallback() : $fallback;
72✔
3173
            }
3174

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

3183
            return $usedArrayTmp;
168✔
3184
        }
3185

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

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

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

3211
        return $return;
90✔
3212
    }
3213

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

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

3262
        $jsonObject = \json_decode($json, false);
48✔
3263

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

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

3274
        return $return;
30✔
3275
    }
3276

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

3288
        return $this->properties;
142✔
3289
    }
3290

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

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

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

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

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

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

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

3407
            return;
30✔
3408
        }
3409

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

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

3430
            return;
468✔
3431
        }
3432

3433
        yield from $this->array;
6,742✔
3434
    }
3435

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3624
            // Add to result.
3625
            if ($groupKey !== null) {
24✔
3626
                $result[$groupKey] = $newValue;
18✔
3627

3628
                if ($saveKeys) {
18✔
3629
                    $result[$groupKey][$key] = $value;
12✔
3630
                } else {
3631
                    $result[$groupKey][] = $value;
6✔
3632
                }
3633
            }
3634
        }
3635

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

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

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

3661
        if (\is_array($key)) {
180✔
3662
            if ($key === []) {
6✔
3663
                return false;
×
3664
            }
3665

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

3673
            return true;
6✔
3674
        }
3675

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3942
        return true;
18✔
3943
    }
3944

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

3960
        if ($keys === null) {
270✔
3961
            return $this->array === [];
258✔
3962
        }
3963

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

3970
        return true;
12✔
3971
    }
3972

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

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

4008
        return false;
108✔
4009
    }
4010

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

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

4030
        return true;
12✔
4031
    }
4032

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

4061
            if ($key !== $i) {
54✔
4062
                return false;
18✔
4063
            }
4064

4065
            ++$i;
48✔
4066
        }
4067

4068
        return !($i === 0);
54✔
4069
    }
4070

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

4081
        return $return;
12✔
4082
    }
4083

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

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

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

4118
        return false;
876✔
4119
    }
4120

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

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

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

4170
        // non recursive
4171

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

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

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

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

4238
        \krsort($this->array, $sort_flags);
24✔
4239

4240
        return $this;
24✔
4241
    }
4242

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

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

4267
        return $that;
24✔
4268
    }
4269

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

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

4293
        return $value_last;
90✔
4294
    }
4295

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

4309
        return \array_key_last($this->array);
126✔
4310
    }
4311

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

4337
        if ($number === null) {
72✔
4338
            $poppedValue = $this->last();
48✔
4339

4340
            if ($poppedValue === null) {
48✔
4341
                $poppedValue = [$poppedValue];
6✔
4342
            } else {
4343
                $poppedValue = (array) $poppedValue;
42✔
4344
            }
4345

4346
            $arrayy = static::create(
48✔
4347
                $poppedValue,
48✔
4348
                $this->iteratorClass,
48✔
4349
                false
48✔
4350
            );
48✔
4351
        } else {
4352
            $arrayy = $this->rest(-$number);
24✔
4353
        }
4354

4355
        return $arrayy;
72✔
4356
    }
4357

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

4378
        $this->array = $this->lastsImmutable($number)->toArray();
72✔
4379
        $this->generator = null;
72✔
4380

4381
        return $this;
72✔
4382
    }
4383

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

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

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

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

4478
        foreach ($this->getGenerator() as $key => $value) {
78✔
4479
            $value = $closure($value, $key);
78✔
4480

4481
            if ($value === false) {
78✔
4482
                return false;
42✔
4483
            }
4484
        }
4485

4486
        return true;
42✔
4487
    }
4488

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

4511
        foreach ($this->getGenerator() as $key => $value) {
72✔
4512
            $value = $closure($value, $key);
72✔
4513

4514
            if ($value === true) {
72✔
4515
                return true;
54✔
4516
            }
4517
        }
4518

4519
        return false;
24✔
4520
    }
4521

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

4538
        $max = false;
60✔
4539
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4540
        foreach ($this->getGeneratorByReference() as &$value) {
60✔
4541
            if (
4542
                $max === false
60✔
4543
                ||
4544
                $value > $max
60✔
4545
            ) {
4546
                $max = $value;
60✔
4547
            }
4548
        }
4549

4550
        return $max;
60✔
4551
    }
4552

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

4587
        return static::create(
198✔
4588
            $result,
198✔
4589
            $this->iteratorClass,
198✔
4590
            false
198✔
4591
        );
198✔
4592
    }
4593

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

4629
        return static::create(
120✔
4630
            $result,
120✔
4631
            $this->iteratorClass,
120✔
4632
            false
120✔
4633
        );
120✔
4634
    }
4635

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

4670
        return static::create(
102✔
4671
            $result,
102✔
4672
            $this->iteratorClass,
102✔
4673
            false
102✔
4674
        );
102✔
4675
    }
4676

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

4712
        return static::create(
126✔
4713
            $result,
126✔
4714
            $this->iteratorClass,
126✔
4715
            false
126✔
4716
        );
126✔
4717
    }
4718

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

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

4748
        $min = false;
60✔
4749
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4750
        foreach ($this->getGeneratorByReference() as &$value) {
60✔
4751
            if (
4752
                $min === false
60✔
4753
                ||
4754
                $value < $min
60✔
4755
            ) {
4756
                $min = $value;
60✔
4757
            }
4758
        }
4759

4760
        return $min;
60✔
4761
    }
4762

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

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

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

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

4838
        return static::create(
6✔
4839
            $output,
6✔
4840
            $this->iteratorClass,
6✔
4841
            false
6✔
4842
        );
6✔
4843
    }
4844

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

4863
        if ($this->offsetExists($key)) {
6✔
4864
            $tmpValue = $this->get($key);
6✔
4865
            unset($array[$key]);
6✔
4866
            $array = [$key => $tmpValue] + $array;
6✔
4867
        }
4868

4869
        return static::create(
6✔
4870
            $array,
6✔
4871
            $this->iteratorClass,
6✔
4872
            false
6✔
4873
        );
6✔
4874
    }
4875

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

4894
        if ($this->offsetExists($key)) {
6✔
4895
            $tmpValue = $this->get($key);
6✔
4896
            unset($array[$key]);
6✔
4897
            $array += [$key => $tmpValue];
6✔
4898
        }
4899

4900
        return static::create(
6✔
4901
            $array,
6✔
4902
            $this->iteratorClass,
6✔
4903
            false
6✔
4904
        );
6✔
4905
    }
4906

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

4920
            return $this->generator->current() ?? false;
×
4921
        }
4922

4923
        return \next($this->array);
×
4924
    }
4925

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

4947
                yield $key => $value;
6✔
4948
            }
4949
        };
6✔
4950

4951
        return static::create(
6✔
4952
            $arrayFunction,
6✔
4953
            $this->iteratorClass,
6✔
4954
            false
6✔
4955
        );
6✔
4956
    }
4957

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

4974
        $generator = function () use ($keys): \Generator {
6✔
4975
            foreach ($this->getGenerator() as $key => $value) {
6✔
4976
                if (isset($keys[$key])) {
6✔
4977
                    yield $key => $value;
6✔
4978
                }
4979
            }
4980
        };
6✔
4981

4982
        return static::create(
6✔
4983
            $generator,
6✔
4984
            $this->iteratorClass,
6✔
4985
            false
6✔
4986
        );
6✔
4987
    }
4988

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

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

5031
        foreach ($this->getGenerator() as $key => $value) {
6✔
5032
            if ($closure($value, $key)) {
6✔
5033
                $matches[$key] = $value;
6✔
5034
            } else {
5035
                $noMatches[$key] = $value;
6✔
5036
            }
5037
        }
5038

5039
        return [self::create($matches), self::create($noMatches)];
6✔
5040
    }
5041

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

5054
        return \array_pop($this->array);
30✔
5055
    }
5056

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

5078
        if ($this->properties !== []) {
72✔
5079
            $this->checkType($key, $value);
24✔
5080
        }
5081

5082
        if ($key === null) {
60✔
5083
            \array_unshift($this->array, $value);
48✔
5084
        } else {
5085
            $this->array = [$key => $value] + $this->array; // @phpstan-ignore assign.propertyType
18✔
5086
        }
5087

5088
        return $this;
60✔
5089
    }
5090

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

5116
            if ($key !== null) {
6✔
5117
                yield $key => $value;
×
5118
            } else {
5119
                yield $value;
6✔
5120
            }
5121

5122
            foreach ($this->getGenerator() as $keyOld => $itemOld) {
6✔
5123
                yield $keyOld => $itemOld;
6✔
5124
            }
5125
        };
6✔
5126

5127
        return static::create(
6✔
5128
            $generator,
6✔
5129
            $this->iteratorClass,
6✔
5130
            false
6✔
5131
        );
6✔
5132
    }
5133

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

5150
        foreach ($this->getGenerator() as $key => $item) {
60✔
5151
            if ($item instanceof self) {
54✔
5152
                $result[$key] = $item->prependToEachKey($suffix);
×
5153
            } elseif (\is_array($item)) {
54✔
5154
                $result[$key] = self::create(
×
5155
                    $item,
×
5156
                    $this->iteratorClass,
×
5157
                    false
×
5158
                )->prependToEachKey($suffix)
×
5159
                    ->toArray();
×
5160
            } else {
5161
                $result[$key . $suffix] = $item;
54✔
5162
            }
5163
        }
5164

5165
        return self::create(
60✔
5166
            $result,
60✔
5167
            $this->iteratorClass,
60✔
5168
            false
60✔
5169
        );
60✔
5170
    }
5171

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

5188
        foreach ($this->getGenerator() as $key => $item) {
60✔
5189
            if ($item instanceof self) {
54✔
5190
                $result[$key] = $item->prependToEachValue($suffix);
×
5191
            } elseif (\is_array($item)) {
54✔
5192
                $result[$key] = self::create(
×
5193
                    $item,
×
5194
                    $this->iteratorClass,
×
5195
                    false
×
5196
                )->prependToEachValue($suffix)
×
5197
                    ->toArray();
×
5198
            } elseif (\is_object($item) === true) {
54✔
5199
                $result[$key] = $item;
6✔
5200
            } else {
5201
                $result[$key] = $item . $suffix;
48✔
5202
            }
5203
        }
5204

5205
        return self::create(
60✔
5206
            $result,
60✔
5207
            $this->iteratorClass,
60✔
5208
            false
60✔
5209
        );
60✔
5210
    }
5211

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

5231
            return $array;
6✔
5232
        }
5233

5234
        if (\is_array($keyOrKeys)) {
30✔
5235
            $valueOrValues = [];
6✔
5236
            foreach ($keyOrKeys as $key) {
6✔
5237
                $valueOrValues[] = $this->get($key, $fallback);
6✔
5238
                $this->offsetUnset($key);
6✔
5239
            }
5240
        } else {
5241
            $valueOrValues = $this->get($keyOrKeys, $fallback);
30✔
5242
            $this->offsetUnset($keyOrKeys);
30✔
5243
        }
5244

5245
        /** @var T|T[]|TFallback $valueOrValues */
5246
        return $valueOrValues;
30✔
5247
    }
5248

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

5266
        if (
5267
            $this->checkPropertyTypes
54✔
5268
            &&
5269
            $this->properties !== []
54✔
5270
        ) {
5271
            foreach ($args as $key => $value) {
18✔
5272
                $this->checkType($key, $value);
18✔
5273
            }
5274
        }
5275

5276
        \array_push($this->array, ...$args); // @phpstan-ignore assign.propertyType
48✔
5277

5278
        return $this;
48✔
5279
    }
5280

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

5299
        if ($this->count() === 0) {
114✔
5300
            return static::create(
6✔
5301
                [],
6✔
5302
                $this->iteratorClass,
6✔
5303
                false
6✔
5304
            );
6✔
5305
        }
5306

5307
        if ($number === null) {
108✔
5308
            $arrayRandValue = [$this->array[\array_rand($this->array)]];
78✔
5309

5310
            return static::create(
78✔
5311
                $arrayRandValue,
78✔
5312
                $this->iteratorClass,
78✔
5313
                false
78✔
5314
            );
78✔
5315
        }
5316

5317
        $arrayTmp = $this->array;
36✔
5318
        \shuffle($arrayTmp);
36✔
5319

5320
        return static::create(
36✔
5321
            $arrayTmp,
36✔
5322
            $this->iteratorClass,
36✔
5323
            false
36✔
5324
        )->firstsImmutable($number);
36✔
5325
    }
5326

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

5346
        if (!isset($result[0])) {
24✔
5347
            $result[0] = null;
×
5348
        }
5349

5350
        return $result[0];
24✔
5351
    }
5352

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

5373
        $count = $this->count();
78✔
5374

5375
        if (
5376
            $number === 0
78✔
5377
            ||
5378
            $number > $count
78✔
5379
        ) {
5380
            throw new \RangeException(
12✔
5381
                \sprintf(
12✔
5382
                    'Number of requested keys (%s) must be equal or lower than number of elements in this array (%s)',
12✔
5383
                    $number,
12✔
5384
                    $count
12✔
5385
                )
12✔
5386
            );
12✔
5387
        }
5388

5389
        $result = (array) \array_rand($this->array, $number);
66✔
5390

5391
        return static::create(
66✔
5392
            $result,
66✔
5393
            $this->iteratorClass,
66✔
5394
            false
66✔
5395
        );
66✔
5396
    }
5397

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

5416
        if ($this->count() === 0) {
102✔
5417
            return static::create(
×
5418
                [],
×
5419
                $this->iteratorClass,
×
5420
                false
×
5421
            );
×
5422
        }
5423

5424
        if ($number === null) {
102✔
5425
            $arrayRandValue = [$this->array[\array_rand($this->array)]];
42✔
5426
            $this->array = $arrayRandValue; // @phpstan-ignore assign.propertyType
42✔
5427

5428
            return $this;
42✔
5429
        }
5430

5431
        \shuffle($this->array);
66✔
5432

5433
        return $this->firstsMutable($number);
66✔
5434
    }
5435

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

5452
        if (!isset($result[0])) {
24✔
5453
            $result[0] = null;
×
5454
        }
5455

5456
        return $result[0];
24✔
5457
    }
5458

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

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

5499
        foreach ($array as $option => $weight) {
54✔
5500
            if ($this->searchIndex($option) !== false) {
54✔
5501
                for ($i = 0; $i < $weight; ++$i) {
12✔
5502
                    $options[] = $option;
6✔
5503
                }
5504
            }
5505
        }
5506

5507
        return $this->mergeAppendKeepIndex($options)->randomImmutable($number);
54✔
5508
    }
5509

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

5543
        /** @var static $return - help for phpstan */
5544
        $return = static::create(
108✔
5545
            $initial,
108✔
5546
            $this->iteratorClass,
108✔
5547
            false
108✔
5548
        );
108✔
5549

5550
        return $return;
108✔
5551
    }
5552

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

5567
        foreach ($this->getGenerator() as $val) {
84✔
5568
            if (\is_array($val)) {
72✔
5569
                $result[] = static::create($val)->reduce_dimension($unique)->toArray();
30✔
5570
            } else {
5571
                $result[] = [$val];
72✔
5572
            }
5573
        }
5574

5575
        $result = $result === [] ? [] : \array_merge(...$result);
84✔
5576

5577
        $resultArrayy = static::create($result);
84✔
5578

5579
        /**
5580
         * @psalm-suppress ImpureMethodCall - object is already re-created
5581
         * @psalm-suppress InvalidReturnStatement - why?
5582
         */
5583
        return $unique ? $resultArrayy->unique() : $resultArrayy;
84✔
5584
    }
5585

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

5602
        $this->array = \array_values($this->array);
54✔
5603

5604
        return $this;
54✔
5605
    }
5606

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

5631
        foreach ($this->getGenerator() as $key => $value) {
6✔
5632
            if (!$closure($value, $key)) {
6✔
5633
                $filtered[$key] = $value;
6✔
5634
            }
5635
        }
5636

5637
        return static::create(
6✔
5638
            $filtered,
6✔
5639
            $this->iteratorClass,
6✔
5640
            false
6✔
5641
        );
6✔
5642
    }
5643

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

5667
            return static::create(
6✔
5668
                $this->toArray(),
6✔
5669
                $this->iteratorClass,
6✔
5670
                false
6✔
5671
            );
6✔
5672
        }
5673

5674
        $this->internalRemove($key);
126✔
5675

5676
        return static::create(
126✔
5677
            $this->toArray(),
126✔
5678
            $this->iteratorClass,
126✔
5679
            false
126✔
5680
        );
126✔
5681
    }
5682

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

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

5717
        \array_shift($tmpArray);
42✔
5718

5719
        return static::create(
42✔
5720
            $tmpArray,
42✔
5721
            $this->iteratorClass,
42✔
5722
            false
42✔
5723
        );
42✔
5724
    }
5725

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

5743
        \array_pop($tmpArray);
42✔
5744

5745
        return static::create(
42✔
5746
            $tmpArray,
42✔
5747
            $this->iteratorClass,
42✔
5748
            false
42✔
5749
        );
42✔
5750
    }
5751

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

5772
        // init
5773
        $isSequentialArray = $this->isSequential();
48✔
5774

5775
        foreach ($this->array as $key => $item) {
48✔
5776
            if ($item === $value) {
42✔
5777
                unset($this->array[$key]);
42✔
5778
            }
5779
        }
5780

5781
        if ($isSequentialArray) {
48✔
5782
            $this->array = \array_values($this->array);
36✔
5783
        }
5784

5785
        return static::create(
48✔
5786
            $this->array,
48✔
5787
            $this->iteratorClass,
48✔
5788
            false
48✔
5789
        );
48✔
5790
    }
5791

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

5809
        return static::create(
6✔
5810
            \array_fill(0, (int) $times, $this->toArray()),
6✔
5811
            $this->iteratorClass,
6✔
5812
            false
6✔
5813
        );
6✔
5814
    }
5815

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

5841
        /**
5842
         * @psalm-suppress ImpureMethodCall - object is already cloned
5843
         */
5844
        return $that->remove($oldKey)
30✔
5845
            ->set($newKey, $newValue);
30✔
5846
    }
5847

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

5885
        return static::create(
12✔
5886
            $data,
12✔
5887
            $this->iteratorClass,
12✔
5888
            false
12✔
5889
        );
12✔
5890
    }
5891

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

5929
        return static::create(
12✔
5930
            $data,
12✔
5931
            $this->iteratorClass,
12✔
5932
            false
12✔
5933
        );
12✔
5934
    }
5935

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

5961
        return static::create(
6✔
5962
            $result,
6✔
5963
            $this->iteratorClass,
6✔
5964
            false
6✔
5965
        );
6✔
5966
    }
5967

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

5992
        if ($key !== false) {
18✔
5993
            $array[$key] = $replacement;
18✔
5994
        }
5995

5996
        return static::create(
18✔
5997
            $array,
18✔
5998
            $this->iteratorClass,
18✔
5999
            false
18✔
6000
        );
18✔
6001
    }
6002

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

6026
        /* @phpstan-ignore argument.type */
6027
        return $this->each($callable);
6✔
6028
    }
6029

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

6049
        return static::create(
90✔
6050
            \array_splice($tmpArray, $from),
90✔
6051
            $this->iteratorClass,
90✔
6052
            false
90✔
6053
        );
90✔
6054
    }
6055

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

6072
        $this->array = \array_reverse($this->array);
54✔
6073

6074
        return $this;
54✔
6075
    }
6076

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

6093
        $this->array = \array_reverse($this->array, true);
30✔
6094

6095
        return $this;
30✔
6096
    }
6097

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

6116
        \rsort($this->array, $sort_flags);
24✔
6117

6118
        return $this;
24✔
6119
    }
6120

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

6140
        /**
6141
         * @psalm-suppress ImpureMethodCall - object is already cloned
6142
         */
6143
        $that->rsort($sort_flags);
24✔
6144

6145
        return $that;
24✔
6146
    }
6147

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

6173
        return false;
66✔
6174
    }
6175

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

6196
        // init
6197
        $return = [];
54✔
6198

6199
        if ($this->array === []) {
54✔
6200
            return static::create(
×
6201
                [],
×
6202
                $this->iteratorClass,
×
6203
                false
×
6204
            );
×
6205
        }
6206

6207
        // php cast "bool"-index into "int"-index
6208
        /* @phpstan-ignore identical.alwaysFalse */
6209
        if ((bool) $index === $index) {
54✔
6210
            $index = (int) $index;
6✔
6211
        }
6212

6213
        if ($this->offsetExists($index)) {
54✔
6214
            $return = [$this->array[$index]];
42✔
6215
        }
6216

6217
        return static::create(
54✔
6218
            $return,
54✔
6219
            $this->iteratorClass,
54✔
6220
            false
54✔
6221
        );
54✔
6222
    }
6223

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

6246
        return $this;
168✔
6247
    }
6248

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

6273
        // If the key doesn't exist, set it.
6274
        if (!$this->has($key)) {
66✔
6275
            $this->array = $this->set($key, $fallback)->toArray();
24✔
6276
        }
6277

6278
        return $this->get($key);
66✔
6279
    }
6280

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

6293
        return \array_shift($this->array);
30✔
6294
    }
6295

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

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

6338
        foreach ($array as $key => $value) {
12✔
6339
            // check if recursive is needed
6340
            if (\is_array($value)) {
12✔
6341
                /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
6342
                /** @phpstan-var array<TKey,T> $value */
6343
                $value = $value;
×
6344

6345
                $array[$key] = $this->shuffle($secure, $value);
×
6346
            }
6347
        }
6348

6349
        return static::create(
12✔
6350
            $array,
12✔
6351
            $this->iteratorClass,
12✔
6352
            false
12✔
6353
        );
12✔
6354
    }
6355

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

6370
    /**
6371
     * Checks whether array has exactly $size items.
6372
     *
6373
     * @param int $size
6374
     *
6375
     * @return bool
6376
     */
6377
    public function sizeIs(int $size): bool
6378
    {
6379
        // init
6380
        $itemsTempCount = 0;
6✔
6381

6382
        /** @noinspection PhpUnusedLocalVariableInspection */
6383
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
6384
        foreach ($this->getGeneratorByReference() as &$value) {
6✔
6385
            ++$itemsTempCount;
6✔
6386
            if ($itemsTempCount > $size) {
6✔
6387
                return false;
6✔
6388
            }
6389
        }
6390

6391
        return $itemsTempCount === $size;
6✔
6392
    }
6393

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

6411
        // init
6412
        $itemsTempCount = 0;
6✔
6413

6414
        /** @noinspection PhpUnusedLocalVariableInspection */
6415
        foreach ($this->getGenerator() as $value) {
6✔
6416
            ++$itemsTempCount;
6✔
6417
            if ($itemsTempCount > $toSize) {
6✔
6418
                return false;
6✔
6419
            }
6420
        }
6421

6422
        return $fromSize < $itemsTempCount && $itemsTempCount < $toSize;
6✔
6423
    }
6424

6425
    /**
6426
     * Checks whether array has more than $size items.
6427
     *
6428
     * @param int $size
6429
     *
6430
     * @return bool
6431
     */
6432
    public function sizeIsGreaterThan(int $size): bool
6433
    {
6434
        // init
6435
        $itemsTempCount = 0;
6✔
6436

6437
        /** @noinspection PhpUnusedLocalVariableInspection */
6438
        foreach ($this->getGenerator() as $value) {
6✔
6439
            ++$itemsTempCount;
6✔
6440
            if ($itemsTempCount > $size) {
6✔
6441
                return true;
6✔
6442
            }
6443
        }
6444

6445
        return $itemsTempCount > $size;
6✔
6446
    }
6447

6448
    /**
6449
     * Checks whether array has less than $size items.
6450
     *
6451
     * @param int $size
6452
     *
6453
     * @return bool
6454
     */
6455
    public function sizeIsLessThan(int $size): bool
6456
    {
6457
        // init
6458
        $itemsTempCount = 0;
6✔
6459

6460
        /** @noinspection PhpUnusedLocalVariableInspection */
6461
        foreach ($this->getGenerator() as $value) {
6✔
6462
            ++$itemsTempCount;
6✔
6463
            if ($itemsTempCount > $size) {
6✔
6464
                return false;
6✔
6465
            }
6466
        }
6467

6468
        return $itemsTempCount < $size;
6✔
6469
    }
6470

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

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

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

6558
        return $this->sorting(
120✔
6559
            $this->array,
120✔
6560
            $direction,
120✔
6561
            $strategy,
120✔
6562
            $keepKeys
120✔
6563
        );
120✔
6564
    }
6565

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

6586
        $that->generatorToArray();
72✔
6587

6588
        return $that->sorting(
72✔
6589
            $that->array,
72✔
6590
            $direction,
72✔
6591
            $strategy,
72✔
6592
            $keepKeys
72✔
6593
        );
72✔
6594
    }
6595

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

6621
        $this->sorterKeys($this->array, $direction, $strategy);
108✔
6622

6623
        return $this;
108✔
6624
    }
6625

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

6648
        /**
6649
         * @psalm-suppress ImpureMethodCall - object is already cloned
6650
         */
6651
        $that->sortKeys($direction, $strategy);
48✔
6652

6653
        return $that;
48✔
6654
    }
6655

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

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

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

6734
        // Transform all values into their results.
6735
        if ($sorter) {
6✔
6736
            $arrayy = static::create(
6✔
6737
                $array,
6✔
6738
                $this->iteratorClass,
6✔
6739
                false
6✔
6740
            );
6✔
6741

6742
            /**
6743
             * @psalm-suppress MissingClosureReturnType
6744
             * @psalm-suppress MissingClosureParamType
6745
             */
6746
            $results = $arrayy->each(
6✔
6747
                static function ($value) use ($sorter) {
6✔
6748
                    if (\is_callable($sorter) === true) {
6✔
6749
                        return $sorter($value);
6✔
6750
                    }
6751

6752
                    return $sorter === $value;
6✔
6753
                }
6✔
6754
            );
6✔
6755

6756
            $results = $results->toArray();
6✔
6757
        } else {
6758
            $results = $array;
6✔
6759
        }
6760

6761
        // Sort by the results and replace by original values
6762
        \array_multisort($results, $direction, $strategy, $array);
6✔
6763

6764
        return static::create(
6✔
6765
            $array,
6✔
6766
            $this->iteratorClass,
6✔
6767
            false
6✔
6768
        );
6✔
6769
    }
6770

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

6787
        \array_splice(
6✔
6788
            $tmpArray,
6✔
6789
            $offset,
6✔
6790
            $length ?? $this->count(),
6✔
6791
            $replacement
6✔
6792
        );
6✔
6793

6794
        return static::create(
6✔
6795
            $tmpArray,
6✔
6796
            $this->iteratorClass,
6✔
6797
            false
6✔
6798
        );
6✔
6799
    }
6800

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

6826
                    if ($i % $numberOfPieces !== 0) {
6✔
6827
                        ++$i;
6✔
6828

6829
                        continue;
6✔
6830
                    }
6831

6832
                    yield $carry;
6✔
6833

6834
                    $carry = [];
6✔
6835
                    $i = 1;
6✔
6836
                }
6837

6838
                if ($carry !== []) {
6✔
6839
                    yield $carry;
6✔
6840
                }
6841
            };
6✔
6842
        } else {
6843
            $generator = function () use ($numberOfPieces) {
6✔
6844
                $carry = [];
6✔
6845
                $i = 1;
6✔
6846
                foreach ($this->getGenerator() as $value) {
6✔
6847
                    $carry[] = $value;
6✔
6848

6849
                    if ($i % $numberOfPieces !== 0) {
6✔
6850
                        ++$i;
6✔
6851

6852
                        continue;
6✔
6853
                    }
6854

6855
                    yield $carry;
6✔
6856

6857
                    $carry = [];
6✔
6858
                    $i = 1;
6✔
6859
                }
6860

6861
                if ($carry !== []) {
6✔
6862
                    yield $carry;
6✔
6863
                }
6864
            };
6✔
6865
        }
6866

6867
        return static::create(
6✔
6868
            $generator,
6✔
6869
            $this->iteratorClass,
6✔
6870
            false
6✔
6871
        );
6✔
6872
    }
6873

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

6895
                if ((bool) \trim((string) $item)) {
6✔
6896
                    yield $key => $item;
6✔
6897
                }
6898
            }
6899
        };
6✔
6900

6901
        return static::create(
6✔
6902
            $generator(),
6✔
6903
            $this->iteratorClass,
6✔
6904
            false
6✔
6905
        );
6✔
6906
    }
6907

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

6928
        list($array[$swapA], $array[$swapB]) = [$array[$swapB], $array[$swapA]];
6✔
6929

6930
        return static::create(
6✔
6931
            $array,
6✔
6932
            $this->iteratorClass,
6✔
6933
            false
6✔
6934
        );
6✔
6935
    }
6936

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

6962
            foreach ($this->getGenerator() as $key => $value) {
18✔
6963
                if ($value instanceof self) {
18✔
6964
                    $value = $value->toArray(
12✔
6965
                        $convertAllArrayyElements,
12✔
6966
                        $preserveKeys
12✔
6967
                    );
12✔
6968
                }
6969

6970
                if ($preserveKeys) {
18✔
6971
                    $array[$key] = $value;
12✔
6972
                } else {
6973
                    $array[] = $value;
6✔
6974
                }
6975
            }
6976

6977
            /* @phpstan-ignore return.type */
6978
            return $array;
18✔
6979
        }
6980

6981
        return \iterator_to_array($this->getGenerator(), $preserveKeys);
5,772✔
6982
    }
6983

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

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

7023
        $return = \json_encode($this->toArray(), $options, $depth);
78✔
7024
        if ($return === false) {
78✔
7025
            return '';
×
7026
        }
7027

7028
        return $return;
78✔
7029
    }
7030

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

7044
        if ($items === null) {
6✔
7045
            $items = $this->toArray();
6✔
7046
        }
7047

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

7066
        /** @var static $return  - help for phpstan */
7067
        $return = static::create(
6✔
7068
            $return,
6✔
7069
            $this->iteratorClass,
6✔
7070
            false
6✔
7071
        );
6✔
7072

7073
        return $return;
6✔
7074
    }
7075

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

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

7105
        $this->array = $this->reduce(
78✔
7106
            static function ($resultArray, $value, $key) {
78✔
7107
                if (!\in_array($value, $resultArray, true)) {
72✔
7108
                    $resultArray[] = $value;
72✔
7109
                }
7110

7111
                return $resultArray;
72✔
7112
            },
78✔
7113
            []
78✔
7114
        )->toArray();
78✔
7115
        $this->generator = null;
78✔
7116

7117
        return $this;
78✔
7118
    }
7119

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

7136
        // init
7137
        $array = $this->toArray();
66✔
7138

7139
        /**
7140
         * @psalm-suppress MissingClosureReturnType
7141
         * @psalm-suppress MissingClosureParamType
7142
         */
7143
        $this->array = \array_reduce(
66✔
7144
            \array_keys($array),
66✔
7145
            static function ($resultArray, $key) use ($array) {
66✔
7146
                if (!\in_array($array[$key], $resultArray, true)) {
60✔
7147
                    $resultArray[$key] = $array[$key];
60✔
7148
                }
7149

7150
                return $resultArray;
60✔
7151
            },
66✔
7152
            []
66✔
7153
        );
66✔
7154
        $this->generator = null;
66✔
7155

7156
        return $this;
66✔
7157
    }
7158

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

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

7189
        if (
7190
            $this->checkPropertyTypes
36✔
7191
            &&
7192
            $this->properties !== []
36✔
7193
        ) {
7194
            foreach ($args as $key => $value) {
12✔
7195
                $this->checkType($key, $value);
12✔
7196
            }
7197
        }
7198

7199
        \array_unshift($this->array, ...$args); // @phpstan-ignore assign.propertyType
30✔
7200

7201
        return $this;
30✔
7202
    }
7203

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

7222
        return true;
6✔
7223
    }
7224

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

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

7289
        if ($this->array !== []) {
72✔
7290
            if ($recursive === true) {
60✔
7291
                if ($userData !== self::ARRAYY_HELPER_WALK) {
30✔
7292
                    \array_walk_recursive($this->array, $callable, $userData);
×
7293
                } else {
7294
                    \array_walk_recursive($this->array, $callable);
30✔
7295
                }
7296
            } else {
7297
                if ($userData !== self::ARRAYY_HELPER_WALK) {
30✔
7298
                    \array_walk($this->array, $callable, $userData);
×
7299
                } else {
7300
                    /* @phpstan-ignore argument.type */
7301
                    \array_walk($this->array, $callable);
30✔
7302
                }
7303
            }
7304
        }
7305

7306
        return $this;
72✔
7307
    }
7308

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

7332
                return $accessorValue === $value;
×
7333
            }
6✔
7334
        );
6✔
7335
    }
7336

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

7351
        if (\count($array, \COUNT_NORMAL) <= 0) {
24✔
7352
            return $object;
6✔
7353
        }
7354

7355
        foreach ($array as $name => $value) {
18✔
7356
            if (\is_array($value)) {
18✔
7357
                $object->{$name} = static::arrayToObject($value);
6✔
7358
            } else {
7359
                $object->{$name} = $value;
18✔
7360
            }
7361
        }
7362

7363
        return $object;
18✔
7364
    }
7365

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

7398
        if ($input === null) {
66✔
7399
            $input = $this->getGenerator();
24✔
7400
        }
7401

7402
        if ($search_values === null) {
66✔
7403
            foreach ($input as $key => $value) {
66✔
7404
                $keys[] = $key;
66✔
7405

7406
                // check if recursive is needed
7407
                if (\is_array($value)) {
66✔
7408
                    $keysTmp[] = $this->array_keys_recursive($value);
24✔
7409
                }
7410
            }
7411
        } else {
7412
            $is_array_tmp = \is_array($search_values);
6✔
7413

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

7441
                // check if recursive is needed
7442
                if (\is_array($value)) {
6✔
7443
                    $keysTmp[] = $this->array_keys_recursive($value);
6✔
7444
                }
7445
            }
7446
        }
7447

7448
        return $keysTmp === [] ? $keys : \array_merge($keys, ...$keysTmp);
66✔
7449
    }
7450

7451
    /**
7452
     * @param string     $path
7453
     * @param callable   $callable
7454
     * @param array|null $currentOffset
7455
     *
7456
     * @return void
7457
     *
7458
     * @phpstan-param array<array-key,mixed>|null $currentOffset
7459
     * @psalm-mutation-free
7460
     */
7461
    protected function callAtPath($path, $callable, &$currentOffset = null)
7462
    {
7463
        $this->generatorToArray();
60✔
7464

7465
        if ($currentOffset === null) {
60✔
7466
            $currentOffset = &$this->array;
60✔
7467
        }
7468

7469
        $explodedPath = \explode($this->pathSeparator, $path);
60✔
7470
        /* @phpstan-ignore identical.alwaysFalse */
7471
        if ($explodedPath === false) {
60✔
7472
            return;
×
7473
        }
7474

7475
        $nextPath = \array_shift($explodedPath);
60✔
7476
        if (!isset($currentOffset[$nextPath])) {
60✔
7477
            return;
6✔
7478
        }
7479

7480
        if ($explodedPath !== []) {
54✔
7481
            $this->callAtPath(
6✔
7482
                \implode($this->pathSeparator, $explodedPath),
6✔
7483
                $callable,
6✔
7484
                $currentOffset[$nextPath]
6✔
7485
            );
6✔
7486
        } else {
7487
            $callable($currentOffset[$nextPath]);
54✔
7488
        }
7489
    }
7490

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

7511
            if ($return instanceof self) {
6✔
7512
                return $return->toArray();
×
7513
            }
7514

7515
            return $return;
6✔
7516
        }
7517

NEW
7518
        if (\is_object($object) && \property_exists($object, $keyOrPropertyOrMethod)) {
×
7519
            return $object->{$keyOrPropertyOrMethod};
×
7520
        }
7521

NEW
7522
        if (\is_object($object) && \method_exists($object, $keyOrPropertyOrMethod)) {
×
7523
            return $object->{$keyOrPropertyOrMethod}();
×
7524
        }
7525

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

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

7552
        if ($data === null) {
7,828✔
7553
            throw new \InvalidArgumentException('Passed value should be a array');
12✔
7554
        }
7555

7556
        return $data;
7,816✔
7557
    }
7558

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

7576
            return true;
18✔
7577
        }
7578

7579
        return false;
7,228✔
7580
    }
7581

7582
    /**
7583
     * Get correct PHP constant for direction.
7584
     *
7585
     * @param int|string $direction
7586
     *
7587
     * @return int
7588
     * @psalm-mutation-free
7589
     */
7590
    protected function getDirection($direction): int
7591
    {
7592
        if ((string) $direction === $direction) {
258✔
7593
            $direction = \strtolower($direction);
60✔
7594

7595
            if ($direction === 'desc') {
60✔
7596
                $direction = \SORT_DESC;
12✔
7597
            } else {
7598
                $direction = \SORT_ASC;
54✔
7599
            }
7600
        }
7601

7602
        if (
7603
            $direction !== \SORT_DESC
258✔
7604
            &&
7605
            $direction !== \SORT_ASC
258✔
7606
        ) {
7607
            $direction = \SORT_ASC;
×
7608
        }
7609

7610
        return $direction;
258✔
7611
    }
7612

7613
    /**
7614
     * @return TypeCheckInterface[]
7615
     *
7616
     * @noinspection ReturnTypeCanBeDeclaredInspection
7617
     */
7618
    protected function getPropertiesFromPhpDoc()
7619
    {
7620
        static $PROPERTY_CACHE = [];
448✔
7621
        static $OPTIONAL_PROPERTY_CACHE = [];
448✔
7622
        $cacheKey = 'Class::' . static::class;
448✔
7623

7624
        if (isset($PROPERTY_CACHE[$cacheKey])) {
448✔
7625
            $this->optionalProperties = $OPTIONAL_PROPERTY_CACHE[$cacheKey] ?? [];
400✔
7626

7627
            return $PROPERTY_CACHE[$cacheKey];
400✔
7628
        }
7629

7630
        $properties = $this->getPropertiesFromNativeDefinitions();
124✔
7631
        $optionalProperties = [];
124✔
7632
        $phpDocPropertyAnnotationStyle = null;
124✔
7633

7634
        $reflector = new \ReflectionClass($this);
124✔
7635
        $factory = \phpDocumentor\Reflection\DocBlockFactory::createInstance();
124✔
7636
        $docComment = $reflector->getDocComment();
124✔
7637
        if ($docComment) {
124✔
7638
            $docblock = $factory->create($docComment);
118✔
7639
            $this->addPropertiesFromDocBlock($docblock, $properties, $optionalProperties, $phpDocPropertyAnnotationStyle);
118✔
7640
        }
7641

7642
        /** @noinspection PhpAssignmentInConditionInspection */
7643
        while ($reflector = $reflector->getParentClass()) {
118✔
7644
            $docComment = $reflector->getDocComment();
118✔
7645
            if ($docComment) {
118✔
7646
                $docblock = $factory->create($docComment);
118✔
7647
                $this->addPropertiesFromDocBlock($docblock, $properties, $optionalProperties, $phpDocPropertyAnnotationStyle);
118✔
7648
            }
7649
        }
7650

7651
        $this->optionalProperties = $optionalProperties;
112✔
7652
        $OPTIONAL_PROPERTY_CACHE[$cacheKey] = $optionalProperties;
112✔
7653

7654
        return $PROPERTY_CACHE[$cacheKey] = $properties;
112✔
7655
    }
7656

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

7672
        if ($propertyTags !== [] && $arrayShapeItems !== []) {
124✔
7673
            throw new \TypeError('Use either @property tags or array-shape annotations for Arrayy property definitions, not both.');
6✔
7674
        }
7675

7676
        $currentPhpDocPropertyAnnotationStyle = null;
118✔
7677
        if ($propertyTags !== []) {
118✔
7678
            $currentPhpDocPropertyAnnotationStyle = 'property';
42✔
7679
        } elseif ($arrayShapeItems !== []) {
118✔
7680
            $currentPhpDocPropertyAnnotationStyle = 'array-shape';
48✔
7681
        }
7682

7683
        if (
7684
            $currentPhpDocPropertyAnnotationStyle !== null
118✔
7685
            &&
7686
            $phpDocPropertyAnnotationStyle !== null
118✔
7687
            &&
7688
            $phpDocPropertyAnnotationStyle !== $currentPhpDocPropertyAnnotationStyle
118✔
7689
        ) {
7690
            throw new \TypeError('Use either @property tags or array-shape annotations for Arrayy property definitions, not both.');
6✔
7691
        }
7692

7693
        if ($currentPhpDocPropertyAnnotationStyle !== null) {
118✔
7694
            $phpDocPropertyAnnotationStyle = $currentPhpDocPropertyAnnotationStyle;
84✔
7695
        }
7696

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

7714
        foreach ($arrayShapeItems as $item) {
118✔
7715
            $typeName = (string) $item->getKey();
48✔
7716
            if ($typeName === '') {
48✔
7717
                continue;
×
7718
            }
7719

7720
            $typeName = \trim($typeName, '\'"');
48✔
7721
            if (isset($properties[$typeName])) {
48✔
7722
                continue;
×
7723
            }
7724

7725
            $typeCheckPhpDoc = TypeCheckPhpDoc::fromDocTypeObject($typeName, $item->getValue());
48✔
7726
            $properties[$typeName] = $typeCheckPhpDoc;
48✔
7727
            if ($item->isOptional()) {
48✔
7728
                $optionalProperties[$typeName] = true;
30✔
7729
            }
7730
        }
7731
    }
7732

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

7746
        $items = [];
124✔
7747
        foreach ($docblock->getTagsByName('template') as $tag) {
124✔
7748
            if (
7749
                $tag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Template
124✔
7750
                &&
7751
                $tag->getTemplateName() === 'T'
124✔
7752
                &&
7753
                $tag->getBound() instanceof \phpDocumentor\Reflection\PseudoTypes\ArrayShape
124✔
7754
            ) {
7755
                foreach ($tag->getBound()->getItems() as $item) {
42✔
7756
                    $items[] = $item;
42✔
7757
                }
7758
            }
7759
        }
7760

7761
        foreach ($docblock->getTagsByName('extends') as $tag) {
124✔
7762
            if (!$tag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Extends_) {
124✔
7763
                continue;
×
7764
            }
7765

7766
            $type = $tag->getType();
124✔
7767
            if (
7768
                !$type instanceof \phpDocumentor\Reflection\PseudoTypes\Generic
124✔
7769
                ||
7770
                !$this->isArrayyGenericTarget((string) $type->getFqsen())
124✔
7771
            ) {
7772
                continue;
112✔
7773
            }
7774

7775
            foreach ($type->getTypes() as $genericType) {
118✔
7776
                if ($genericType instanceof \phpDocumentor\Reflection\PseudoTypes\ArrayShape) {
118✔
7777
                    foreach ($genericType->getItems() as $item) {
12✔
7778
                        $items[] = $item;
12✔
7779
                    }
7780
                }
7781
            }
7782
        }
7783

7784
        return $items;
124✔
7785
    }
7786

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

7801
        if (\in_array($fqcn, [self::class, ArrayyStrict::class], true)) {
124✔
7802
            return true;
118✔
7803
        }
7804

7805
        return \class_exists($fqcn) && \is_a($fqcn, self::class, true);
112✔
7806
    }
7807

7808
    /**
7809
     * @return TypeCheckInterface[]
7810
     */
7811
    protected function getPropertiesFromNativeDefinitions(): array
7812
    {
7813
        $properties = [];
124✔
7814
        $reflector = new \ReflectionClass($this);
124✔
7815
        $reservedProperties = self::getReservedPropertyNames();
124✔
7816

7817
        do {
7818
            if ($reflector->getName() === self::class) {
124✔
7819
                break;
124✔
7820
            }
7821

7822
            foreach ($reflector->getProperties() as $property) {
124✔
7823
                if (
7824
                    $property->getDeclaringClass()->getName() !== $reflector->getName()
124✔
7825
                    ||
7826
                    $property->isStatic()
106✔
7827
                    ||
7828
                    isset($reservedProperties[$property->getName()])
106✔
7829
                    ||
7830
                    isset($properties[$property->getName()])
124✔
7831
                ) {
7832
                    continue;
124✔
7833
                }
7834

7835
                $properties[$property->getName()] = TypeCheckPhpDoc::fromReflectionProperty($property);
16✔
7836
            }
7837
        } while ($reflector = $reflector->getParentClass());
124✔
7838

7839
        return $properties;
124✔
7840
    }
7841

7842
    /**
7843
     * @return array<string, true>
7844
     */
7845
    private static function getReservedPropertyNames(): array
7846
    {
7847
        static $reservedProperties = null;
124✔
7848

7849
        if ($reservedProperties !== null) {
124✔
7850
            return $reservedProperties;
118✔
7851
        }
7852

7853
        $reservedProperties = [];
6✔
7854
        $reflector = new \ReflectionClass(self::class);
6✔
7855
        foreach ($reflector->getProperties() as $property) {
6✔
7856
            if ($property->getDeclaringClass()->getName() !== self::class) {
6✔
7857
                continue;
×
7858
            }
7859

7860
            $reservedProperties[$property->getName()] = true;
6✔
7861
        }
7862

7863
        return $reservedProperties;
6✔
7864
    }
7865

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

7885
        if (\is_array($pieces)) {
222✔
7886
            /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
7887
            /** @phpstan-var array<TKey,T> $pieces */
7888
            $pieces = $pieces;
222✔
7889

7890
            $pieces_count = \count($pieces, \COUNT_NORMAL);
222✔
7891
            $pieces_count_not_zero = $pieces_count > 0;
222✔
7892

7893
            return \implode(
222✔
7894
                $glue,
222✔
7895
                \array_map(
222✔
7896
                    [$this, 'implode_recursive'],
222✔
7897
                    \array_fill(0, ($pieces_count_not_zero ? $pieces_count : 1), $glue),
222✔
7898
                    ($useKeys === true && $pieces_count_not_zero ? $this->array_keys_recursive($pieces) : $pieces)
222✔
7899
                )
222✔
7900
            );
222✔
7901
        }
7902

7903
        if (
7904
            \is_scalar($pieces) === true
222✔
7905
            ||
7906
            $pieces instanceof \Stringable
222✔
7907
        ) {
7908
            return (string) $pieces;
198✔
7909
        }
7910

7911
        return '';
48✔
7912
    }
7913

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

7945
        foreach ($haystack as $item) {
111✔
7946
            if (\is_array($item)) {
87✔
7947
                $returnTmp = $this->in_array_recursive($needle, $item, $strict);
21✔
7948
            } else {
7949
                /** @noinspection NestedPositiveIfStatementsInspection */
7950
                if ($strict === true) {
87✔
7951
                    $returnTmp = $item === $needle;
87✔
7952
                } else {
7953
                    $returnTmp = $item == $needle;
×
7954
                }
7955
            }
7956

7957
            if ($returnTmp === true) {
87✔
7958
                return true;
63✔
7959
            }
7960
        }
7961

7962
        return false;
48✔
7963
    }
7964

7965
    /**
7966
     * @param mixed $data
7967
     *
7968
     * @return array<mixed>|null
7969
     */
7970
    protected function internalGetArray(&$data)
7971
    {
7972
        if (\is_array($data)) {
7,828✔
7973
            return $data;
7,792✔
7974
        }
7975

7976
        if (!$data) {
678✔
7977
            return [];
42✔
7978
        }
7979

7980
        if (\is_object($data) === true) {
672✔
7981
            if ($data instanceof \ArrayObject) {
630✔
7982
                return $data->getArrayCopy();
30✔
7983
            }
7984

7985
            if ($data instanceof \Generator) {
606✔
7986
                return static::createFromGeneratorImmutable($data)->toArray();
6✔
7987
            }
7988

7989
            if ($data instanceof \Traversable) {
600✔
7990
                return static::createFromObject($data)->toArray();
×
7991
            }
7992

7993
            if ($data instanceof \JsonSerializable) {
600✔
7994
                return (array) $data->jsonSerialize();
×
7995
            }
7996

7997
            if (\method_exists($data, '__toArray')) {
600✔
7998
                return (array) $data->__toArray();
×
7999
            }
8000

8001
            if (\method_exists($data, '__toString')) {
600✔
8002
                return [(string) $data];
×
8003
            }
8004
        }
8005

8006
        if (\is_callable($data)) {
642✔
8007
            /**
8008
             * @psalm-suppress InvalidPropertyAssignmentValue - why?
8009
             */
8010
            $this->generator = new ArrayyRewindableGenerator($data);
588✔
8011

8012
            return [];
588✔
8013
        }
8014

8015
        if (\is_scalar($data)) {
66✔
8016
            return [$data];
54✔
8017
        }
8018

8019
        return null;
12✔
8020
    }
8021

8022
    /**
8023
     * Internal mechanics of remove method.
8024
     *
8025
     * @param float|int|string $key
8026
     *
8027
     * @return bool
8028
     */
8029
    protected function internalRemove($key): bool
8030
    {
8031
        $this->generatorToArray();
132✔
8032

8033
        if (
8034
            $this->pathSeparator
132✔
8035
            &&
8036
            (string) $key === $key
132✔
8037
            &&
8038
            \strpos($key, $this->pathSeparator) !== false
132✔
8039
        ) {
8040
            $path = \explode($this->pathSeparator, (string) $key);
×
8041
            // crawl though the keys
8042
            while (\count($path, \COUNT_NORMAL) > 1) {
×
8043
                $key = \array_shift($path);
×
8044

8045
                if (!$this->has($key)) {
×
8046
                    return false;
×
8047
                }
8048

8049
                $this->array = &$this->array[$key];
×
8050
            }
8051

8052
            $key = \array_shift($path);
×
8053
        }
8054

8055
        if ($key === null) {
132✔
8056
            return false;
6✔
8057
        }
8058

8059
        if (\is_float($key)) {
126✔
NEW
8060
            return false;
×
8061
        }
8062

8063
        unset($this->array[$key]);
126✔
8064

8065
        return true;
126✔
8066
    }
8067

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

8093
        if ($key === null) {
6,850✔
8094
            return false;
×
8095
        }
8096

8097
        $this->generatorToArray();
6,850✔
8098

8099
        $array = &$this->array;
6,850✔
8100

8101
        /**
8102
         * https://github.com/vimeo/psalm/issues/2536
8103
         *
8104
         * @psalm-suppress PossiblyInvalidArgument
8105
         * @psalm-suppress InvalidScalarArgument
8106
         */
8107
        if (
8108
            $this->pathSeparator
6,850✔
8109
            &&
8110
            (string) $key === $key
6,850✔
8111
            &&
8112
            \strpos($key, $this->pathSeparator) !== false
6,850✔
8113
        ) {
8114
            $path = \explode($this->pathSeparator, (string) $key);
54✔
8115
            // crawl through the keys
8116
            while (\count($path, \COUNT_NORMAL) > 1) {
54✔
8117
                $key = \array_shift($path);
54✔
8118

8119
                $array = &$array[$key];
54✔
8120
            }
8121

8122
            $key = \array_shift($path);
54✔
8123
        }
8124

8125
        if ($array === null) {
6,850✔
8126
            $array = [];
24✔
8127
        } elseif (!\is_array($array)) {
6,832✔
8128
            throw new \RuntimeException('Can not set value at this path "' . $key . '" because (' . \gettype($array) . ')"' . \print_r($array, true) . '" is not an array.');
6✔
8129
        }
8130

8131
        $array[$key] = $value;
6,850✔
8132

8133
        return true;
6,850✔
8134
    }
8135

8136
    /**
8137
     * Convert a object into an array.
8138
     *
8139
     * @param mixed|object $object
8140
     *
8141
     * @return array|mixed
8142
     *
8143
     * @psalm-mutation-free
8144
     */
8145
    protected static function objectToArray($object)
8146
    {
8147
        if (!\is_object($object)) {
36✔
8148
            return $object;
30✔
8149
        }
8150

8151
        $object = \get_object_vars($object);
36✔
8152

8153
        /**
8154
         * @psalm-suppress PossiblyInvalidArgument - the parameter is always some kind of array - false-positive from psalm?
8155
         */
8156
        return \array_map([static::class, 'objectToArray'], $object);
36✔
8157
    }
8158

8159
    /**
8160
     * @param array $data
8161
     * @param bool  $checkPropertiesInConstructor
8162
     *
8163
     * @return void
8164
     *
8165
     * @phpstan-param array<mixed,T> $data
8166
     */
8167
    protected function setInitialValuesAndProperties(array &$data, bool $checkPropertiesInConstructor)
8168
    {
8169
        $checkPropertiesInConstructor = $this->checkForMissingPropertiesInConstructor === true
7,816✔
8170
                                        &&
7,816✔
8171
                                        $checkPropertiesInConstructor === true;
7,816✔
8172

8173
        if ($this->properties === []) {
7,816✔
8174
            if (
8175
                $this->checkPropertyTypes === true
7,222✔
8176
                ||
8177
                $checkPropertiesInConstructor === true
7,222✔
8178
            ) {
8179
                $this->properties = $this->getPropertiesFromPhpDoc();
424✔
8180
            }
8181

8182
            /** @var TypeCheckInterface[] $properties */
8183
            $properties = $this->properties;
7,210✔
8184
            $requiredProperties = \array_diff_key($properties, $this->optionalProperties);
7,210✔
8185

8186
            if (
8187
                $this->checkPropertiesMismatchInConstructor === true
7,210✔
8188
                &&
8189
                \count($data) !== 0
7,210✔
8190
                &&
8191
                \count(\array_diff_key($requiredProperties, $data)) > 0
7,210✔
8192
            ) {
8193
                throw new \TypeError('Property mismatch - input: ' . \print_r(\array_keys($data), true) . ' | expected: ' . \print_r(\array_keys($requiredProperties), true));
12✔
8194
            }
8195
        }
8196

8197
        foreach ($data as $key => &$valueInner) {
7,792✔
8198
            $this->internalSet(
6,778✔
8199
                $key,
6,778✔
8200
                $valueInner,
6,778✔
8201
                $checkPropertiesInConstructor
6,778✔
8202
            );
6,778✔
8203
        }
8204
    }
8205

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

8227
        switch ($direction) {
8228
            case 'desc':
108✔
8229
            case \SORT_DESC:
8230
                \krsort($elements, $strategy);
36✔
8231

8232
                break;
36✔
8233
            case 'asc':
78✔
8234
            case \SORT_ASC:
78✔
8235
            default:
8236
                \ksort($elements, $strategy);
78✔
8237
        }
8238

8239
        return $this;
108✔
8240
    }
8241

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

8263
        if (!$strategy) {
144✔
8264
            $strategy = \SORT_REGULAR;
144✔
8265
        }
8266

8267
        switch ($direction) {
8268
            case 'desc':
144✔
8269
            case \SORT_DESC:
8270
                if ($keepKeys) {
78✔
8271
                    \arsort($elements, $strategy);
54✔
8272
                } else {
8273
                    \rsort($elements, $strategy);
24✔
8274
                }
8275

8276
                break;
78✔
8277
            case 'asc':
66✔
8278
            case \SORT_ASC:
66✔
8279
            default:
8280
                if ($keepKeys) {
66✔
8281
                    \asort($elements, $strategy);
24✔
8282
                } else {
8283
                    \sort($elements, $strategy);
42✔
8284
                }
8285
        }
8286

8287
        return $this;
144✔
8288
    }
8289

8290
    /**
8291
     * @param array $array
8292
     *
8293
     * @return array
8294
     *
8295
     * @phpstan-param array<array-key, mixed> $array
8296
     * @phpstan-return array<array-key, mixed>
8297
     *
8298
     * @psalm-mutation-free
8299
     */
8300
    private function getArrayRecursiveHelperArrayy(array $array)
8301
    {
8302
        if ($array === []) {
150✔
8303
            return [];
×
8304
        }
8305

8306
        \array_walk_recursive(
150✔
8307
            $array,
150✔
8308
            /**
8309
             * @param array|self $item
8310
             *
8311
             * @return void
8312
             */
8313
            static function (&$item) {
150✔
8314
                if ($item instanceof self) {
150✔
8315
                    $item = $item->getArray();
6✔
8316
                }
8317
            }
150✔
8318
        );
150✔
8319

8320
        return $array;
150✔
8321
    }
8322

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

8341
        if (isset($this->properties[self::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES])) {
1,020✔
8342
            $this->properties[self::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES]->checkType($value);
714✔
8343
        } elseif ($key !== null && isset($this->properties[$key])) {
372✔
8344
            $this->properties[$key]->checkType($value);
372✔
8345
        }
8346
    }
8347
}
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