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

voku / Arrayy / 30197880199

26 Jul 2026 10:13AM UTC coverage: 91.369% (-1.0%) from 92.38%
30197880199

Pull #179

github

web-flow
Merge dfd198411 into 57c7fca16
Pull Request #179: Add PHPStan dot-notation return-type extension and harden typing/fixtures

117 of 179 new or added lines in 8 files covered. (65.36%)

2784 of 3047 relevant lines covered (91.37%)

246.75 hits per line

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

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

3
declare(strict_types=1);
4

5
namespace Arrayy;
6

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

176
        return $return;
×
177
    }
178

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

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

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

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

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

248
        if (\is_array($return) === true) {
1,181 ✔
249
            $return = static::create(
×
NEW
250
                [], // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
×
251
                $this->iteratorClass,
×
252
                false
×
253
            )->createByReference($return);
×
254
        }
255

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

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

285
            $this->internalSet($key, $value); // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
35 ✔
286

287
            return $this;
28 ✔
288
        }
289

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

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

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

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

333
        return $this;
140 ✔
334
    }
335

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

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

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

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

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

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

403
        return $this;
28 ✔
404
    }
405

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

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

430
        return $that;
28 ✔
431
    }
432

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

573
        return $return;
×
574
    }
575

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

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

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

609
        return $this;
28 ✔
610
    }
611

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

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

635
        return $that;
28 ✔
636
    }
637

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

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

653
        return $this;
56 ✔
654
    }
655

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

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

674
        return $that;
28 ✔
675
    }
676

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

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

692
        return $this;
70 ✔
693
    }
694

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

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

713
        return $that;
28 ✔
714
    }
715

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

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

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

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

769
        return $offsetExists;
987 ✔
770
    }
771

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

790
        if ($this->offsetExists($offset)) {
1,174 ✔
791
            $value = &$this->__get($offset); // @phpstan-ignore-line argument.templateType, argument.type (the dynamic value is intentionally forwarded through an invariant generic boundary)
1,160 ✔
792
        }
793

794
        return $value; // @phpstan-ignore return.type (offsetGet() intentionally returns the referenced value selected at runtime)
1,174 ✔
795
    }
796

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

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

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

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

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

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

845
            return;
98 ✔
846
        }
847

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

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

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

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

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

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

916
            return;
8,993 ✔
917
        }
918

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

928
                return;
×
929
            }
930
        }
931

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

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

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

955
        return $this;
56 ✔
956
    }
957

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

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

981
        return $that;
28 ✔
982
    }
983

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

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

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

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

1063
        if ($key !== null) {
7 ✔
1064
            if (
1065
                isset($this->array[$key])
7 ✔
1066
                &&
1067
                \is_array($this->array[$key])
7 ✔
1068
            ) {
1069
                foreach ($values as $value) {
7 ✔
1070
                    $this->array[$key][] = $value; // @phpstan-ignore-line assign.propertyType (runtime normalization intentionally rebuilds the generic backing array)
7 ✔
1071
                }
1072
            } else {
1073
                foreach ($values as $value) {
3 ✔
1074
                    $this->array[$key] = $value;
×
1075
                }
1076
            }
1077
        } else {
1078
            foreach ($values as $value) {
×
1079
                $this->array[] = $value;
×
1080
            }
1081
        }
1082

1083
        return $this;
7 ✔
1084
    }
1085

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

1102
        foreach ($this->getGenerator() as $key => $item) {
70 ✔
1103
            if ($item instanceof self) {
63 ✔
1104
                $result[$prefix . $key] = $item->appendToEachKey($prefix);
×
1105
            } elseif (\is_array($item)) {
63 ✔
NEW
1106
                $result[$prefix . $key] = self::create($item, $this->iteratorClass, false) // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
×
1107
                    ->appendToEachKey($prefix)
×
1108
                    ->toArray();
×
1109
            } else {
1110
                $result[$prefix . $key] = $item;
63 ✔
1111
            }
1112
        }
1113

1114
        return self::create(
70 ✔
1115
            $result, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
70 ✔
1116
            $this->iteratorClass,
70 ✔
1117
            false
70 ✔
1118
        );
70 ✔
1119
    }
1120

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

1137
        foreach ($this->getGenerator() as $key => $item) {
70 ✔
1138
            if ($item instanceof self) {
63 ✔
1139
                $result[$key] = $item->appendToEachValue($prefix);
×
1140
            } elseif (\is_array($item)) {
63 ✔
NEW
1141
                $result[$key] = self::create($item, $this->iteratorClass, false)->appendToEachValue($prefix)->toArray(); // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
×
1142
            } elseif (\is_object($item) === true) {
63 ✔
1143
                $result[$key] = $item;
7 ✔
1144
            } else {
1145
                $result[$key] = $prefix . $item;
56 ✔
1146
            }
1147
        }
1148

1149
        return self::create($result, $this->iteratorClass, false); // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
70 ✔
1150
    }
1151

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

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

1166
        return $this;
28 ✔
1167
    }
1168

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

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

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

1186
        return $that;
70 ✔
1187
    }
1188

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

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

1217
        return static::create(
21 ✔
1218
            $that->toArray(), // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
21 ✔
1219
            $this->iteratorClass,
21 ✔
1220
            false
21 ✔
1221
        );
21 ✔
1222
    }
1223

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

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

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

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

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

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

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

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

1309
        return static::create(
7 ✔
1310
            $return, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
1311
            $this->iteratorClass,
7 ✔
1312
            false
7 ✔
1313
        );
7 ✔
1314
    }
1315

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

1332
        return $this;
84 ✔
1333
    }
1334

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

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

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

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

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

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

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

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

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

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

1449
            return $this;
21 ✔
1450
        }
1451

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

1455
        return $this;
49 ✔
1456
    }
1457

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

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

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

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

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

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

1532
        return false;
49 ✔
1533
    }
1534

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

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

1569
            return false;
56 ✔
1570
        }
1571

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

1579
        return false;
28 ✔
1580
    }
1581

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

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

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

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

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

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

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

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

1743
        return $return;
49 ✔
1744
    }
1745

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

1773
        return $instance;
5,513 ✔
1774
    }
1775

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

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

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

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

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

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

1837
        return $this;
197 ✔
1838
    }
1839

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

1857
    /**
1858
     * Create an new instance filled with a copy of values from a "Generator"-object.
1859
     *
1860
     * @param \Generator $generator
1861
     *
1862
     * @return static
1863
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1864
     *
1865
     * @phpstan-param \Generator<TKey,T> $generator
1866
     * @phpstan-return static
1867
     * @psalm-mutation-free
1868
     */
1869
    public static function createFromGeneratorImmutable(\Generator $generator): self
1870
    {
1871
        return self::create(\iterator_to_array($generator, true)); // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
35 ✔
1872
    }
1873

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

1890
    /**
1891
     * Create an new Arrayy object via JSON.
1892
     *
1893
     * @param array $array
1894
     *
1895
     * @return static
1896
     *                <p>(Immutable) Returns an new instance of the Arrayy object.</p>
1897
     *
1898
     * @phpstan-param array<TKey,T> $array
1899
     * @phpstan-return static
1900
     * @psalm-mutation-free
1901
     */
1902
    public static function createFromArray(array $array): self
1903
    {
1904
        return static::create($array); // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
1905
    }
1906

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

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

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

1937
        return $arrayy;
28 ✔
1938
    }
1939

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

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

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

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

2000
        /** @var static $return - help for phpstan */
2001
        $return = static::create($array); // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
70 ✔
2002

2003
        return $return;
70 ✔
2004
    }
2005

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

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

2044
        return $return;
14 ✔
2045
    }
2046

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

2060
        return \current($this->array);
×
2061
    }
2062

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

2093
        \uksort($this->array, $callable);
35 ✔
2094

2095
        return $this;
35 ✔
2096
    }
2097

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

2118
        $that->generatorToArray();
7 ✔
2119

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

2125
        return $that;
7 ✔
2126
    }
2127

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

2156
        \usort($this->array, $callable);
77 ✔
2157

2158
        return $this;
77 ✔
2159
    }
2160

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

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

2186
        return $that;
35 ✔
2187
    }
2188

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2367
        return static::create(
7 ✔
2368
            $result, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
2369
            $this->iteratorClass,
7 ✔
2370
            false
7 ✔
2371
        );
7 ✔
2372
    }
2373

2374
    /**
2375
     * Return elements where the values that are only in the new $array.
2376
     *
2377
     * EXAMPLE: <code>
2378
     * a([1 => 1])->diffReverse([1 => 1, 2 => 2]); // Arrayy[2 => 2]
2379
     * </code>
2380
     *
2381
     * @param array $array
2382
     *
2383
     * @return static
2384
     *                <p>(Immutable)</p>
2385
     *
2386
     * @phpstan-param  array<TKey,T> $array
2387
     * @phpstan-return static
2388
     * @psalm-mutation-free
2389
     */
2390
    public function diffReverse(array $array = []): self
2391
    {
2392
        return static::create(
56 ✔
2393
            \array_diff($array, $this->toArray()), // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
56 ✔
2394
            $this->iteratorClass,
56 ✔
2395
            false
56 ✔
2396
        );
56 ✔
2397
    }
2398

2399
    /**
2400
     * Divide an array into two arrays. One with keys and the other with values.
2401
     *
2402
     * EXAMPLE: <code>
2403
     * a(['a' => 1, 'b' => ''])->divide(); // Arrayy[Arrayy['a', 'b'], Arrayy[1, '']]
2404
     * </code>
2405
     *
2406
     * @return static
2407
     *                <p>(Immutable)</p>
2408
     *
2409
     * @phpstan-return static
2410
     * @psalm-mutation-free
2411
     */
2412
    public function divide(): self
2413
    {
2414
        return static::create(
7 ✔
2415
            [ // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
2416
                $this->keys(),
7 ✔
2417
                $this->values(),
7 ✔
2418
            ],
7 ✔
2419
            $this->iteratorClass,
7 ✔
2420
            false
7 ✔
2421
        );
7 ✔
2422
    }
2423

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

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

2456
        return static::create( // @phpstan-ignore return.type (create() is intentionally re-parameterized with TEach)
49 ✔
2457
            $array, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
49 ✔
2458
            $this->iteratorClass,
49 ✔
2459
            false
49 ✔
2460
        );
49 ✔
2461
    }
2462

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

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

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

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

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

2516
                break;
7 ✔
2517
            }
2518
        }
2519

2520
        return $isExists;
28 ✔
2521
    }
2522

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

2546
        $this->generatorToArray();
49 ✔
2547

2548
        $tmpArray = $this->array;
49 ✔
2549

2550
        $count = \count($tmpArray);
49 ✔
2551

2552
        while ($count < $num) {
49 ✔
2553
            $tmpArray[] = $default;
28 ✔
2554
            ++$count;
28 ✔
2555
        }
2556

2557
        return static::create(
49 ✔
2558
            $tmpArray, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
49 ✔
2559
            $this->iteratorClass,
49 ✔
2560
            false
49 ✔
2561
        );
49 ✔
2562
    }
2563

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

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

2623
            $generator = function () use ($closure) {
91 ✔
2624
                foreach ($this->getGenerator() as $key => $value) {
84 ✔
2625
                    if ($closure($value, $key) === true) {
77 ✔
2626
                        yield $key => $value;
70 ✔
2627
                    }
2628
                }
2629
            };
91 ✔
2630
        } else {
2631
            $generator = function () use ($closure) {
7 ✔
2632
                foreach ($this->getGenerator() as $key => $value) {
7 ✔
2633
                    if ($closure($value) === true) {
7 ✔
2634
                        yield $key => $value;
7 ✔
2635
                    }
2636
                }
2637
            };
7 ✔
2638
        }
2639

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

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

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

2725
        $result = \array_values(
7 ✔
2726
            \array_filter(
7 ✔
2727
                $this->toArray(false, true),
7 ✔
2728
                static function ($item) use (
7 ✔
2729
                    $property,
7 ✔
2730
                    $value,
7 ✔
2731
                    $ops,
7 ✔
2732
                    $comparisonOp
7 ✔
2733
                ) {
7 ✔
2734
                    $item = (array) $item;
7 ✔
2735
                    $itemArrayy = static::create($item); // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
2736
                    $item[$property] = $itemArrayy->get($property, []);
7 ✔
2737

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

2743
        return static::create(
7 ✔
2744
            $result, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
2745
            $this->iteratorClass,
7 ✔
2746
            false
7 ✔
2747
        );
7 ✔
2748
    }
2749

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

2777
        return false;
21 ✔
2778
    }
2779

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

2807
        return false;
28 ✔
2808
    }
2809

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

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

2857
        return $this->get($key_first);
134 ✔
2858
    }
2859

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

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

2877
        return $return;
204 ✔
2878
    }
2879

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

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

2906
        return static::create(
259 ✔
2907
            $array, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
259 ✔
2908
            $this->iteratorClass,
259 ✔
2909
            false
259 ✔
2910
        );
259 ✔
2911
    }
2912

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

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

2935
        return static::create(
21 ✔
2936
            $array, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
21 ✔
2937
            $this->iteratorClass,
21 ✔
2938
            false
21 ✔
2939
        );
21 ✔
2940
    }
2941

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

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

2970
        return $this;
238 ✔
2971
    }
2972

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

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

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

3042
            return clone $this;
7 ✔
3043
        }
3044

3045
        if ($array !== null) {
2,000 ✔
3046
            if ($useByReference) {
28 ✔
3047
                $usedArray = &$array;
×
3048
            } else {
3049
                $usedArray = $array;
28 ✔
3050
            }
3051
        } else {
3052
            $this->generatorToArray();
1,979 ✔
3053

3054
            if ($useByReference) {
1,979 ✔
3055
                $usedArray = &$this->array;
1,181 ✔
3056
            } else {
3057
                $usedArray = $this->array;
932 ✔
3058
            }
3059
        }
3060

3061
        if ($key === null) {
2,000 ✔
3062
            return static::create(
7 ✔
3063
                [], // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
3064
                $this->iteratorClass,
7 ✔
3065
                false
7 ✔
3066
            )->createByReference($usedArray);
7 ✔
3067
        }
3068

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

3075
        if (\array_key_exists($key, $usedArray) === true) {
2,000 ✔
3076
            if (\is_array($usedArray[$key])) {
1,727 ✔
3077
                return static::create(
141 ✔
3078
                    [], // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
141 ✔
3079
                    $this->iteratorClass,
141 ✔
3080
                    false
141 ✔
3081
                )->createByReference($usedArray[$key]);
141 ✔
3082
            }
3083

3084
            return $usedArray[$key];
1,615 ✔
3085
        }
3086

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

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

3112
                    continue;
217 ✔
3113
                }
3114

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

3122
                    continue;
7 ✔
3123
                }
3124

3125
                if ($segments[0] === '*') {
98 ✔
3126
                    $segmentsTmp = $segments;
7 ✔
3127
                    unset($segmentsTmp[0]);
7 ✔
3128
                    $keyTmp = \implode('.', $segmentsTmp);
7 ✔
3129
                    $returnTmp = static::create(
7 ✔
3130
                        [], // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
3131
                        $this->iteratorClass,
7 ✔
3132
                        false
7 ✔
3133
                    );
7 ✔
3134
                    foreach ($this->getAll() as $dataTmp) {
7 ✔
3135
                        if ($dataTmp instanceof self) {
7 ✔
3136
                            $returnTmp->add($dataTmp->get($keyTmp));
×
3137

3138
                            continue;
×
3139
                        }
3140

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

3152
                            continue;
×
3153
                        }
3154

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

3162
                            continue;
7 ✔
3163
                        }
3164
                    }
3165

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

3171
                return $fallback instanceof \Closure ? $fallback() : $fallback;
91 ✔
3172
            }
3173

3174
            if (\is_array($usedArrayTmp)) {
203 ✔
3175
                return static::create(
42 ✔
3176
                    [], // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
42 ✔
3177
                    $this->iteratorClass,
42 ✔
3178
                    false
42 ✔
3179
                )->createByReference($usedArrayTmp);
42 ✔
3180
            }
3181

3182
            return $usedArrayTmp;
203 ✔
3183
        }
3184

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

3189
        return static::create(
×
NEW
3190
            [], // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
×
3191
            $this->iteratorClass,
×
3192
            false
×
3193
        )->createByReference($usedArray);
×
3194
    }
3195

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

3210
        return $return;
105 ✔
3211
    }
3212

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

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

3261
        $jsonObject = \json_decode($json, false);
56 ✔
3262

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

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

3273
        return $return;
35 ✔
3274
    }
3275

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

3287
        return $this->properties;
166 ✔
3288
    }
3289

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

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

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

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

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

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

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

3406
            return;
35 ✔
3407
        }
3408

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

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

3429
            return;
560 ✔
3430
        }
3431

3432
        yield from $this->array;
7,922 ✔
3433
    }
3434

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

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

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

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

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

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

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

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

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

3566
        return static::create(
28 ✔
3567
            \array_values($this->array), // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
28 ✔
3568
            $this->iteratorClass,
28 ✔
3569
            false
28 ✔
3570
        );
28 ✔
3571
    }
3572

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

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

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

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

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

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

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

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

3635
        return static::create(
28 ✔
3636
            $result, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
28 ✔
3637
            $this->iteratorClass,
28 ✔
3638
            false
28 ✔
3639
        );
28 ✔
3640
    }
3641

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

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

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

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

3672
            return true;
7 ✔
3673
        }
3674

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

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

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

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

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

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

3750
        return static::create(
28 ✔
3751
            $results, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
28 ✔
3752
            $this->iteratorClass,
28 ✔
3753
            false
28 ✔
3754
        );
28 ✔
3755
    }
3756

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

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

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

3832
        return static::create(
21 ✔
3833
            \array_values(\array_intersect($this->toArray(), $search)), // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
21 ✔
3834
            $this->iteratorClass,
21 ✔
3835
            false
21 ✔
3836
        );
21 ✔
3837
    }
3838

3839
    /**
3840
     * Return an array with all elements found in input array.
3841
     *
3842
     * @param array ...$array
3843
     *
3844
     * @return static
3845
     *                <p>(Immutable)</p>
3846
     *
3847
     * @phpstan-param  array<array<TKey,T>> ...$array
3848
     * @phpstan-return static
3849
     * @psalm-mutation-free
3850
     */
3851
    public function intersectionMulti(...$array): self
3852
    {
3853
        return static::create(
7 ✔
3854
            \array_values(\array_intersect($this->toArray(), ...$array)), // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
3855
            $this->iteratorClass,
7 ✔
3856
            false
7 ✔
3857
        );
7 ✔
3858
    }
3859

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

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

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

3909
        return static::create(
7 ✔
3910
            $array, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
3911
            $this->iteratorClass,
7 ✔
3912
            false
7 ✔
3913
        );
7 ✔
3914
    }
3915

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

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

3941
        return true;
21 ✔
3942
    }
3943

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

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

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

3969
        return true;
14 ✔
3970
    }
3971

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

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

4007
        return false;
126 ✔
4008
    }
4009

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

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

4029
        return true;
14 ✔
4030
    }
4031

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

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

4064
            ++$i;
56 ✔
4065
        }
4066

4067
        return !($i === 0);
63 ✔
4068
    }
4069

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

4080
        return $return;
14 ✔
4081
    }
4082

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

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

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

4117
        return false;
1,036 ✔
4118
    }
4119

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

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

4162
            return static::create(
28 ✔
4163
                $array, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
28 ✔
4164
                $this->iteratorClass,
28 ✔
4165
                false
28 ✔
4166
            );
28 ✔
4167
        }
4168

4169
        // non recursive
4170

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

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

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

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

4237
        \krsort($this->array, $sort_flags);
28 ✔
4238

4239
        return $this;
28 ✔
4240
    }
4241

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

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

4266
        return $that;
28 ✔
4267
    }
4268

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

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

4292
        return $value_last;
105 ✔
4293
    }
4294

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

4308
        return \array_key_last($this->array);
147 ✔
4309
    }
4310

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

4336
        if ($number === null) {
84 ✔
4337
            $poppedValue = $this->last();
56 ✔
4338

4339
            if ($poppedValue === null) {
56 ✔
4340
                $poppedValue = [$poppedValue];
7 ✔
4341
            } else {
4342
                $poppedValue = (array) $poppedValue;
49 ✔
4343
            }
4344

4345
            $arrayy = static::create(
56 ✔
4346
                $poppedValue, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
56 ✔
4347
                $this->iteratorClass,
56 ✔
4348
                false
56 ✔
4349
            );
56 ✔
4350
        } else {
4351
            $arrayy = $this->rest(-$number);
28 ✔
4352
        }
4353

4354
        return $arrayy;
84 ✔
4355
    }
4356

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

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

4380
        return $this;
84 ✔
4381
    }
4382

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

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

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

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

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

4480
            if ($value === false) {
91 ✔
4481
                return false;
49 ✔
4482
            }
4483
        }
4484

4485
        return true;
49 ✔
4486
    }
4487

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

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

4513
            if ($value === true) {
84 ✔
4514
                return true;
63 ✔
4515
            }
4516
        }
4517

4518
        return false;
28 ✔
4519
    }
4520

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

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

4549
        return $max;
70 ✔
4550
    }
4551

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

4586
        return static::create(
231 ✔
4587
            $result, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
231 ✔
4588
            $this->iteratorClass,
231 ✔
4589
            false
231 ✔
4590
        );
231 ✔
4591
    }
4592

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

4628
        return static::create(
140 ✔
4629
            $result, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
140 ✔
4630
            $this->iteratorClass,
140 ✔
4631
            false
140 ✔
4632
        );
140 ✔
4633
    }
4634

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

4669
        return static::create(
119 ✔
4670
            $result, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
119 ✔
4671
            $this->iteratorClass,
119 ✔
4672
            false
119 ✔
4673
        );
119 ✔
4674
    }
4675

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

4711
        return static::create(
147 ✔
4712
            $result, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
147 ✔
4713
            $this->iteratorClass,
147 ✔
4714
            false
147 ✔
4715
        );
147 ✔
4716
    }
4717

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

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

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

4759
        return $min;
70 ✔
4760
    }
4761

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

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

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

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

4836
        return static::create(
7 ✔
4837
            $output, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
4838
            $this->iteratorClass,
7 ✔
4839
            false
7 ✔
4840
        );
7 ✔
4841
    }
4842

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

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

4867
        return static::create(
7 ✔
4868
            $array, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
4869
            $this->iteratorClass,
7 ✔
4870
            false
7 ✔
4871
        );
7 ✔
4872
    }
4873

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

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

4898
        return static::create(
7 ✔
4899
            $array, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
4900
            $this->iteratorClass,
7 ✔
4901
            false
7 ✔
4902
        );
7 ✔
4903
    }
4904

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

4918
            return $this->generator->current() ?? false;
×
4919
        }
4920

4921
        return \next($this->array);
×
4922
    }
4923

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

4945
                yield $key => $value;
7 ✔
4946
            }
4947
        };
7 ✔
4948

4949
        return static::create(
7 ✔
4950
            $arrayFunction,
7 ✔
4951
            $this->iteratorClass,
7 ✔
4952
            false
7 ✔
4953
        );
7 ✔
4954
    }
4955

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

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

4980
        return static::create(
7 ✔
4981
            $generator,
7 ✔
4982
            $this->iteratorClass,
7 ✔
4983
            false
7 ✔
4984
        );
7 ✔
4985
    }
4986

4987
    /**
4988
     * Pad array to the specified size with a given value.
4989
     *
4990
     * @param int   $size  <p>Size of the result array.</p>
4991
     * @param mixed $value <p>Empty value by default.</p>
4992
     *
4993
     * @return static
4994
     *                <p>(Immutable) Arrayy object padded to $size with $value.</p>
4995
     *
4996
     * @phpstan-return static
4997
     * @psalm-mutation-free
4998
     */
4999
    public function pad(int $size, $value): self
5000
    {
5001
        return static::create(
35 ✔
5002
            \array_pad($this->toArray(), $size, $value), // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
35 ✔
5003
            $this->iteratorClass,
35 ✔
5004
            false
35 ✔
5005
        );
35 ✔
5006
    }
5007

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

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

5037
        return [self::create($matches), self::create($noMatches)]; // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
5038
    }
5039

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

5052
        return \array_pop($this->array);
35 ✔
5053
    }
5054

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

5076
        if ($this->properties !== []) {
84 ✔
5077
            $this->checkType($key, $value);
28 ✔
5078
        }
5079

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

5086
        return $this;
70 ✔
5087
    }
5088

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

5114
            if ($key !== null) {
7 ✔
5115
                yield $key => $value;
×
5116
            } else {
5117
                yield $value;
7 ✔
5118
            }
5119

5120
            foreach ($this->getGenerator() as $keyOld => $itemOld) {
7 ✔
5121
                yield $keyOld => $itemOld;
7 ✔
5122
            }
5123
        };
7 ✔
5124

5125
        return static::create(
7 ✔
5126
            $generator,
7 ✔
5127
            $this->iteratorClass,
7 ✔
5128
            false
7 ✔
5129
        );
7 ✔
5130
    }
5131

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

5148
        foreach ($this->getGenerator() as $key => $item) {
70 ✔
5149
            if ($item instanceof self) {
63 ✔
5150
                $result[$key] = $item->prependToEachKey($suffix);
×
5151
            } elseif (\is_array($item)) {
63 ✔
5152
                $result[$key] = self::create(
×
NEW
5153
                    $item, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
×
5154
                    $this->iteratorClass,
×
5155
                    false
×
5156
                )->prependToEachKey($suffix)
×
5157
                    ->toArray();
×
5158
            } else {
5159
                $result[$key . $suffix] = $item;
63 ✔
5160
            }
5161
        }
5162

5163
        return self::create(
70 ✔
5164
            $result, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
70 ✔
5165
            $this->iteratorClass,
70 ✔
5166
            false
70 ✔
5167
        );
70 ✔
5168
    }
5169

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

5186
        foreach ($this->getGenerator() as $key => $item) {
70 ✔
5187
            if ($item instanceof self) {
63 ✔
5188
                $result[$key] = $item->prependToEachValue($suffix);
×
5189
            } elseif (\is_array($item)) {
63 ✔
5190
                $result[$key] = self::create(
×
NEW
5191
                    $item, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
×
5192
                    $this->iteratorClass,
×
5193
                    false
×
5194
                )->prependToEachValue($suffix)
×
5195
                    ->toArray();
×
5196
            } elseif (\is_object($item) === true) {
63 ✔
5197
                $result[$key] = $item;
7 ✔
5198
            } else {
5199
                $result[$key] = $item . $suffix;
56 ✔
5200
            }
5201
        }
5202

5203
        return self::create(
70 ✔
5204
            $result, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
70 ✔
5205
            $this->iteratorClass,
70 ✔
5206
            false
70 ✔
5207
        );
70 ✔
5208
    }
5209

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

5229
            return $array;
7 ✔
5230
        }
5231

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

5243
        /** @var T|T[]|TFallback $valueOrValues */
5244
        return $valueOrValues;
35 ✔
5245
    }
5246

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

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

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

5276
        return $this;
56 ✔
5277
    }
5278

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

5297
        if ($this->count() === 0) {
133 ✔
5298
            return static::create(
7 ✔
5299
                [], // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
5300
                $this->iteratorClass,
7 ✔
5301
                false
7 ✔
5302
            );
7 ✔
5303
        }
5304

5305
        if ($number === null) {
126 ✔
5306
            $arrayRandValue = [$this->array[\array_rand($this->array)]];
91 ✔
5307

5308
            return static::create(
91 ✔
5309
                $arrayRandValue, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
91 ✔
5310
                $this->iteratorClass,
91 ✔
5311
                false
91 ✔
5312
            );
91 ✔
5313
        }
5314

5315
        $arrayTmp = $this->array;
42 ✔
5316
        \shuffle($arrayTmp);
42 ✔
5317

5318
        return static::create(
42 ✔
5319
            $arrayTmp, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
42 ✔
5320
            $this->iteratorClass,
42 ✔
5321
            false
42 ✔
5322
        )->firstsImmutable($number);
42 ✔
5323
    }
5324

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

5344
        if (!isset($result[0])) {
28 ✔
5345
            $result[0] = null;
×
5346
        }
5347

5348
        return $result[0];
28 ✔
5349
    }
5350

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

5371
        $count = $this->count();
91 ✔
5372

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

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

5389
        return static::create(
77 ✔
5390
            $result, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
77 ✔
5391
            $this->iteratorClass,
77 ✔
5392
            false
77 ✔
5393
        );
77 ✔
5394
    }
5395

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

5414
        if ($this->count() === 0) {
119 ✔
5415
            return static::create(
×
NEW
5416
                [], // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
×
5417
                $this->iteratorClass,
×
5418
                false
×
5419
            );
×
5420
        }
5421

5422
        if ($number === null) {
119 ✔
5423
            $arrayRandValue = [$this->array[\array_rand($this->array)]];
49 ✔
5424
            $this->array = $arrayRandValue; // @phpstan-ignore assign.propertyType
49 ✔
5425

5426
            return $this;
49 ✔
5427
        }
5428

5429
        \shuffle($this->array);
77 ✔
5430

5431
        return $this->firstsMutable($number);
77 ✔
5432
    }
5433

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

5450
        if (!isset($result[0])) {
28 ✔
5451
            $result[0] = null;
×
5452
        }
5453

5454
        return $result[0];
28 ✔
5455
    }
5456

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

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

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

5505
        return $this->mergeAppendKeepIndex($options)->randomImmutable($number);
63 ✔
5506
    }
5507

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

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

5548
        return $return;
126 ✔
5549
    }
5550

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

5565
        foreach ($this->getGenerator() as $val) {
98 ✔
5566
            if (\is_array($val)) {
84 ✔
5567
                $result[] = static::create($val)->reduce_dimension($unique)->toArray(); // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
35 ✔
5568
            } else {
5569
                $result[] = [$val];
84 ✔
5570
            }
5571
        }
5572

5573
        $result = $result === [] ? [] : \array_merge(...$result);
98 ✔
5574

5575
        $resultArrayy = static::create($result); // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
98 ✔
5576

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

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

5600
        $this->array = \array_values($this->array);
63 ✔
5601

5602
        return $this;
63 ✔
5603
    }
5604

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

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

5635
        return static::create(
7 ✔
5636
            $filtered, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
5637
            $this->iteratorClass,
7 ✔
5638
            false
7 ✔
5639
        );
7 ✔
5640
    }
5641

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

5665
            return static::create(
7 ✔
5666
                $this->toArray(), // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
5667
                $this->iteratorClass,
7 ✔
5668
                false
7 ✔
5669
            );
7 ✔
5670
        }
5671

5672
        $this->internalRemove($key);
161 ✔
5673

5674
        return static::create(
161 ✔
5675
            $this->toArray(), // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
161 ✔
5676
            $this->iteratorClass,
161 ✔
5677
            false
161 ✔
5678
        );
161 ✔
5679
    }
5680

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

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

5715
        \array_shift($tmpArray);
49 ✔
5716

5717
        return static::create(
49 ✔
5718
            $tmpArray, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
49 ✔
5719
            $this->iteratorClass,
49 ✔
5720
            false
49 ✔
5721
        );
49 ✔
5722
    }
5723

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

5741
        \array_pop($tmpArray);
49 ✔
5742

5743
        return static::create(
49 ✔
5744
            $tmpArray, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
49 ✔
5745
            $this->iteratorClass,
49 ✔
5746
            false
49 ✔
5747
        );
49 ✔
5748
    }
5749

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

5770
        // init
5771
        $isSequentialArray = $this->isSequential();
56 ✔
5772

5773
        foreach ($this->array as $key => $item) {
56 ✔
5774
            if ($item === $value) {
49 ✔
5775
                unset($this->array[$key]);
49 ✔
5776
            }
5777
        }
5778

5779
        if ($isSequentialArray) {
56 ✔
5780
            $this->array = \array_values($this->array);
42 ✔
5781
        }
5782

5783
        return static::create(
56 ✔
5784
            $this->array, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
56 ✔
5785
            $this->iteratorClass,
56 ✔
5786
            false
56 ✔
5787
        );
56 ✔
5788
    }
5789

5790
    /**
5791
     * Generate array of repeated arrays.
5792
     *
5793
     * @param int $times <p>How many times has to be repeated.</p>
5794
     *
5795
     * @return static
5796
     *                <p>(Immutable)</p>
5797
     *
5798
     * @phpstan-return static
5799
     * @psalm-mutation-free
5800
     */
5801
    public function repeat($times): self
5802
    {
5803
        if ($times === 0) {
7 ✔
5804
            return static::create([], $this->iteratorClass); // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
5805
        }
5806

5807
        return static::create(
7 ✔
5808
            \array_fill(0, (int) $times, $this->toArray()), // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
5809
            $this->iteratorClass,
7 ✔
5810
            false
7 ✔
5811
        );
7 ✔
5812
    }
5813

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

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

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

5880
        return static::create(
21 ✔
5881
            $data, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
21 ✔
5882
            $this->iteratorClass,
21 ✔
5883
            false
21 ✔
5884
        );
21 ✔
5885
    }
5886

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

5921
        return static::create(
21 ✔
5922
            $data, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
21 ✔
5923
            $this->iteratorClass,
21 ✔
5924
            false
21 ✔
5925
        );
21 ✔
5926
    }
5927

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

5949
        return static::create(
14 ✔
5950
            $result, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
14 ✔
5951
            $this->iteratorClass,
14 ✔
5952
            false
14 ✔
5953
        );
14 ✔
5954
    }
5955

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

5980
        if ($key !== false) {
21 ✔
5981
            $array[$key] = $replacement;
21 ✔
5982
        }
5983

5984
        return static::create(
21 ✔
5985
            $array, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
21 ✔
5986
            $this->iteratorClass,
21 ✔
5987
            false
21 ✔
5988
        );
21 ✔
5989
    }
5990

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

6014
        return $this->each($callable); // @phpstan-ignore return.type (the replacement callback intentionally changes values while preserving the collection class)
7 ✔
6015
    }
6016

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

6036
        return static::create(
105 ✔
6037
            \array_splice($tmpArray, $from), // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
105 ✔
6038
            $this->iteratorClass,
105 ✔
6039
            false
105 ✔
6040
        );
105 ✔
6041
    }
6042

6043
    /**
6044
     * Return the array in the reverse order.
6045
     *
6046
     * EXAMPLE: <code>
6047
     * a([1 => 1, 2 => 2, 3 => 3])->reverse(); // self[3, 2, 1]
6048
     * </code>
6049
     *
6050
     * @return $this
6051
     *               <p>(Mutable) Return this Arrayy object.</p>
6052
     *
6053
     * @phpstan-return static
6054
     */
6055
    public function reverse(): self
6056
    {
6057
        $this->generatorToArray();
63 ✔
6058

6059
        $this->array = \array_reverse($this->array);
63 ✔
6060

6061
        return $this;
63 ✔
6062
    }
6063

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

6080
        $this->array = \array_reverse($this->array, true);
35 ✔
6081

6082
        return $this;
35 ✔
6083
    }
6084

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

6103
        \rsort($this->array, $sort_flags);
28 ✔
6104

6105
        return $this;
28 ✔
6106
    }
6107

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

6127
        /**
6128
         * @psalm-suppress ImpureMethodCall - object is already cloned
6129
         */
6130
        $that->rsort($sort_flags);
28 ✔
6131

6132
        return $that;
28 ✔
6133
    }
6134

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

6160
        return false;
77 ✔
6161
    }
6162

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

6183
        // init
6184
        $return = [];
63 ✔
6185

6186
        if ($this->array === []) {
63 ✔
6187
            return static::create(
×
NEW
6188
                [], // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
×
6189
                $this->iteratorClass,
×
6190
                false
×
6191
            );
×
6192
        }
6193

6194
        // php cast "bool"-index into "int"-index
6195
        /* @phpstan-ignore identical.alwaysFalse */
6196
        if ((bool) $index === $index) {
63 ✔
6197
            $index = (int) $index;
7 ✔
6198
        }
6199

6200
        if ($this->offsetExists($index)) {
63 ✔
6201
            $return = [$this->array[$index]];
49 ✔
6202
        }
6203

6204
        return static::create(
63 ✔
6205
            $return, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
63 ✔
6206
            $this->iteratorClass,
63 ✔
6207
            false
63 ✔
6208
        );
63 ✔
6209
    }
6210

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

6233
        return $this;
196 ✔
6234
    }
6235

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

6260
        // If the key doesn't exist, set it.
6261
        if (!$this->has($key)) {
77 ✔
6262
            $this->array = $this->set($key, $fallback)->toArray();
28 ✔
6263
        }
6264

6265
        return $this->get($key);
77 ✔
6266
    }
6267

6268
    /**
6269
     * Shifts a specified value off the beginning of array.
6270
     *
6271
     * @return mixed|null
6272
     *                    <p>(Mutable) A shifted element from the current array.</p>
6273
     *
6274
     * @phpstan-return T|null
6275
     */
6276
    public function shift()
6277
    {
6278
        $this->generatorToArray();
35 ✔
6279

6280
        return \array_shift($this->array);
35 ✔
6281
    }
6282

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

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

6325
        foreach ($array as $key => $value) {
14 ✔
6326
            // check if recursive is needed
6327
            if (\is_array($value)) {
14 ✔
6328
                /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
6329
                /** @phpstan-var array<TKey,T> $value */
6330
                $value = $value;
×
6331

6332
                $array[$key] = $this->shuffle($secure, $value);
×
6333
            }
6334
        }
6335

6336
        return static::create(
14 ✔
6337
            $array, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
14 ✔
6338
            $this->iteratorClass,
14 ✔
6339
            false
14 ✔
6340
        );
14 ✔
6341
    }
6342

6343
    /**
6344
     * Count the values from the current array.
6345
     *
6346
     * alias: for "Arrayy->count()"
6347
     *
6348
     * @param int $mode
6349
     *
6350
     * @return int
6351
     */
6352
    public function size(int $mode = \COUNT_NORMAL): int
6353
    {
6354
        return $this->count($mode);
140 ✔
6355
    }
6356

6357
    /**
6358
     * Checks whether array has exactly $size items.
6359
     *
6360
     * @param int $size
6361
     *
6362
     * @return bool
6363
     */
6364
    public function sizeIs(int $size): bool
6365
    {
6366
        // init
6367
        $itemsTempCount = 0;
7 ✔
6368

6369
        /** @noinspection PhpUnusedLocalVariableInspection */
6370
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
6371
        foreach ($this->getGeneratorByReference() as &$value) {
7 ✔
6372
            ++$itemsTempCount;
7 ✔
6373
            if ($itemsTempCount > $size) {
7 ✔
6374
                return false;
7 ✔
6375
            }
6376
        }
6377

6378
        return $itemsTempCount === $size;
7 ✔
6379
    }
6380

6381
    /**
6382
     * Checks whether array has between $fromSize to $toSize items. $toSize can be
6383
     * smaller than $fromSize.
6384
     *
6385
     * @param int $fromSize
6386
     * @param int $toSize
6387
     *
6388
     * @return bool
6389
     */
6390
    public function sizeIsBetween(int $fromSize, int $toSize): bool
6391
    {
6392
        if ($fromSize > $toSize) {
7 ✔
6393
            $tmp = $toSize;
7 ✔
6394
            $toSize = $fromSize;
7 ✔
6395
            $fromSize = $tmp;
7 ✔
6396
        }
6397

6398
        // init
6399
        $itemsTempCount = 0;
7 ✔
6400

6401
        /** @noinspection PhpUnusedLocalVariableInspection */
6402
        foreach ($this->getGenerator() as $value) {
7 ✔
6403
            ++$itemsTempCount;
7 ✔
6404
            if ($itemsTempCount > $toSize) {
7 ✔
6405
                return false;
7 ✔
6406
            }
6407
        }
6408

6409
        return $fromSize < $itemsTempCount && $itemsTempCount < $toSize;
7 ✔
6410
    }
6411

6412
    /**
6413
     * Checks whether array has more than $size items.
6414
     *
6415
     * @param int $size
6416
     *
6417
     * @return bool
6418
     */
6419
    public function sizeIsGreaterThan(int $size): bool
6420
    {
6421
        // init
6422
        $itemsTempCount = 0;
7 ✔
6423

6424
        /** @noinspection PhpUnusedLocalVariableInspection */
6425
        foreach ($this->getGenerator() as $value) {
7 ✔
6426
            ++$itemsTempCount;
7 ✔
6427
            if ($itemsTempCount > $size) {
7 ✔
6428
                return true;
7 ✔
6429
            }
6430
        }
6431

6432
        return $itemsTempCount > $size;
7 ✔
6433
    }
6434

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

6447
        /** @noinspection PhpUnusedLocalVariableInspection */
6448
        foreach ($this->getGenerator() as $value) {
7 ✔
6449
            ++$itemsTempCount;
7 ✔
6450
            if ($itemsTempCount > $size) {
7 ✔
6451
                return false;
7 ✔
6452
            }
6453
        }
6454

6455
        return $itemsTempCount < $size;
7 ✔
6456
    }
6457

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

6492
    /**
6493
     * Extract a slice of the array.
6494
     *
6495
     * @param int      $offset       <p>Slice begin index.</p>
6496
     * @param int|null $length       <p>Length of the slice.</p>
6497
     * @param bool     $preserveKeys <p>Whether array keys are preserved or no.</p>
6498
     *
6499
     * @return static
6500
     *                <p>(Immutable) A slice of the original array with length $length.</p>
6501
     *
6502
     * @phpstan-return static
6503
     * @psalm-mutation-free
6504
     */
6505
    public function slice(int $offset, ?int $length = null, bool $preserveKeys = false)
6506
    {
6507
        return static::create(
35 ✔
6508
            \array_slice( // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
35 ✔
6509
                $this->toArray(),
35 ✔
6510
                $offset,
35 ✔
6511
                $length,
35 ✔
6512
                $preserveKeys
35 ✔
6513
            ),
35 ✔
6514
            $this->iteratorClass,
35 ✔
6515
            false
35 ✔
6516
        );
35 ✔
6517
    }
6518

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

6545
        return $this->sorting(
140 ✔
6546
            $this->array,
140 ✔
6547
            $direction,
140 ✔
6548
            $strategy,
140 ✔
6549
            $keepKeys
140 ✔
6550
        );
140 ✔
6551
    }
6552

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

6573
        $that->generatorToArray();
84 ✔
6574

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

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

6608
        $this->sorterKeys($this->array, $direction, $strategy);
126 ✔
6609

6610
        return $this;
126 ✔
6611
    }
6612

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

6635
        /**
6636
         * @psalm-suppress ImpureMethodCall - object is already cloned
6637
         */
6638
        $that->sortKeys($direction, $strategy);
56 ✔
6639

6640
        return $that;
56 ✔
6641
    }
6642

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

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

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

6721
        // Transform all values into their results.
6722
        if ($sorter) {
7 ✔
6723
            $arrayy = static::create(
7 ✔
6724
                $array, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
6725
                $this->iteratorClass,
7 ✔
6726
                false
7 ✔
6727
            );
7 ✔
6728

6729
            /**
6730
             * @psalm-suppress MissingClosureReturnType
6731
             * @psalm-suppress MissingClosureParamType
6732
             */
6733
            $results = $arrayy->each(
7 ✔
6734
                static function ($value) use ($sorter) {
7 ✔
6735
                    if (\is_callable($sorter) === true) {
7 ✔
6736
                        return $sorter($value);
7 ✔
6737
                    }
6738

6739
                    return $sorter === $value;
7 ✔
6740
                }
7 ✔
6741
            );
7 ✔
6742

6743
            $results = $results->toArray();
7 ✔
6744
        } else {
6745
            $results = $array;
7 ✔
6746
        }
6747

6748
        // Sort by the results and replace by original values
6749
        \array_multisort($results, $direction, $strategy, $array);
7 ✔
6750

6751
        return static::create(
7 ✔
6752
            $array, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
6753
            $this->iteratorClass,
7 ✔
6754
            false
7 ✔
6755
        );
7 ✔
6756
    }
6757

6758
    /**
6759
     * @param int      $offset
6760
     * @param int|null $length
6761
     * @param array    $replacement
6762
     *
6763
     * @return static
6764
     *                <p>(Immutable)</p>
6765
     *
6766
     * @phpstan-param  array<T> $replacement
6767
     * @phpstan-return static
6768
     * @psalm-mutation-free
6769
     */
6770
    public function splice(int $offset, ?int $length = null, $replacement = []): self
6771
    {
6772
        $tmpArray = $this->toArray();
7 ✔
6773

6774
        \array_splice(
7 ✔
6775
            $tmpArray,
7 ✔
6776
            $offset,
7 ✔
6777
            $length ?? $this->count(),
7 ✔
6778
            $replacement
7 ✔
6779
        );
7 ✔
6780

6781
        return static::create(
7 ✔
6782
            $tmpArray, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
6783
            $this->iteratorClass,
7 ✔
6784
            false
7 ✔
6785
        );
7 ✔
6786
    }
6787

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

6813
                    if ($i % $numberOfPieces !== 0) {
7 ✔
6814
                        ++$i;
7 ✔
6815

6816
                        continue;
7 ✔
6817
                    }
6818

6819
                    yield $carry;
7 ✔
6820

6821
                    $carry = [];
7 ✔
6822
                    $i = 1;
7 ✔
6823
                }
6824

6825
                if ($carry !== []) {
7 ✔
6826
                    yield $carry;
7 ✔
6827
                }
6828
            };
7 ✔
6829
        } else {
6830
            $generator = function () use ($numberOfPieces) {
7 ✔
6831
                $carry = [];
7 ✔
6832
                $i = 1;
7 ✔
6833
                foreach ($this->getGenerator() as $value) {
7 ✔
6834
                    $carry[] = $value;
7 ✔
6835

6836
                    if ($i % $numberOfPieces !== 0) {
7 ✔
6837
                        ++$i;
7 ✔
6838

6839
                        continue;
7 ✔
6840
                    }
6841

6842
                    yield $carry;
7 ✔
6843

6844
                    $carry = [];
7 ✔
6845
                    $i = 1;
7 ✔
6846
                }
6847

6848
                if ($carry !== []) {
7 ✔
6849
                    yield $carry;
7 ✔
6850
                }
6851
            };
7 ✔
6852
        }
6853

6854
        return static::create(
7 ✔
6855
            $generator,
7 ✔
6856
            $this->iteratorClass,
7 ✔
6857
            false
7 ✔
6858
        );
7 ✔
6859
    }
6860

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

6882
                if ((bool) \trim((string) $item)) {
7 ✔
6883
                    yield $key => $item;
7 ✔
6884
                }
6885
            }
6886
        };
7 ✔
6887

6888
        return static::create(
7 ✔
6889
            $generator(),
7 ✔
6890
            $this->iteratorClass,
7 ✔
6891
            false
7 ✔
6892
        );
7 ✔
6893
    }
6894

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

6915
        list($array[$swapA], $array[$swapB]) = [$array[$swapB], $array[$swapA]];
7 ✔
6916

6917
        return static::create(
7 ✔
6918
            $array, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
6919
            $this->iteratorClass,
7 ✔
6920
            false
7 ✔
6921
        );
7 ✔
6922
    }
6923

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

6949
            foreach ($this->getGenerator() as $key => $value) {
21 ✔
6950
                if ($value instanceof self) {
21 ✔
6951
                    $value = $value->toArray(
14 ✔
6952
                        $convertAllArrayyElements,
14 ✔
6953
                        $preserveKeys
14 ✔
6954
                    );
14 ✔
6955
                }
6956

6957
                if ($preserveKeys) {
21 ✔
6958
                    $array[$key] = $value;
14 ✔
6959
                } else {
6960
                    $array[] = $value;
7 ✔
6961
                }
6962
            }
6963

6964
            return $array; // @phpstan-ignore return.type (recursive Arrayy conversion produces the documented runtime array shape)
21 ✔
6965
        }
6966

6967
        return \iterator_to_array($this->getGenerator(), $preserveKeys);
6,783 ✔
6968
    }
6969

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

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

7009
        $return = \json_encode($this->toArray(), $options, $depth);
91 ✔
7010
        if ($return === false) {
91 ✔
7011
            return '';
×
7012
        }
7013

7014
        return $return;
91 ✔
7015
    }
7016

7017
    /**
7018
     * @param string[]|null $items  [optional]
7019
     * @param string[]      $helper [optional]
7020
     *
7021
     * @return static|static[]
7022
     *
7023
     * @phpstan-return static
7024
     */
7025
    public function toPermutation(?array $items = null, array $helper = []): self
7026
    {
7027
        // init
7028
        $return = [];
7 ✔
7029

7030
        if ($items === null) {
7 ✔
7031
            $items = $this->toArray();
7 ✔
7032
        }
7033

7034
        if (empty($items)) {
7 ✔
7035
            $return[] = $helper;
7 ✔
7036
        } else {
7037
            for ($i = \count($items) - 1; $i >= 0; --$i) {
7 ✔
7038
                $new_items = $items;
7 ✔
7039
                $new_helper = $helper;
7 ✔
7040
                list($tmp_helper) = \array_splice($new_items, $i, 1);
7 ✔
7041
                /** @noinspection PhpSillyAssignmentInspection */
7042
                /** @var string[] $new_items */
7043
                $new_items = $new_items;
7 ✔
7044
                \array_unshift($new_helper, $tmp_helper);
7 ✔
7045
                $return = \array_merge(
7 ✔
7046
                    $return,
7 ✔
7047
                    $this->toPermutation($new_items, $new_helper)->toArray()
7 ✔
7048
                );
7 ✔
7049
            }
7050
        }
7051

7052
        /** @var static $return  - help for phpstan */
7053
        $return = static::create(
7 ✔
7054
            $return, // @phpstan-ignore-line argument.type (the runtime API intentionally accepts or transforms a value PHPStan cannot reconcile with the invariant template)
7 ✔
7055
            $this->iteratorClass,
7 ✔
7056
            false
7 ✔
7057
        );
7 ✔
7058

7059
        return $return;
7 ✔
7060
    }
7061

7062
    /**
7063
     * Implodes array to a string with specified separator.
7064
     *
7065
     * @param string $separator [optional] <p>The element's separator.</p>
7066
     *
7067
     * @return string
7068
     *                <p>The string representation of array, separated by ",".</p>
7069
     */
7070
    public function toString(string $separator = ','): string
7071
    {
7072
        return $this->implode($separator);
133 ✔
7073
    }
7074

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

7091
        $this->array = $this->reduce(
91 ✔
7092
            static function ($resultArray, $value, $key) {
91 ✔
7093
                if (!\in_array($value, $resultArray, true)) {
84 ✔
7094
                    $resultArray[] = $value;
84 ✔
7095
                }
7096

7097
                return $resultArray;
84 ✔
7098
            },
91 ✔
7099
            []
91 ✔
7100
        )->toArray();
91 ✔
7101
        $this->generator = null;
91 ✔
7102

7103
        return $this;
91 ✔
7104
    }
7105

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

7122
        // init
7123
        $array = $this->toArray();
77 ✔
7124

7125
        /**
7126
         * @psalm-suppress MissingClosureReturnType
7127
         * @psalm-suppress MissingClosureParamType
7128
         */
7129
        $this->array = \array_reduce(
77 ✔
7130
            \array_keys($array),
77 ✔
7131
            static function ($resultArray, $key) use ($array) {
77 ✔
7132
                if (!\in_array($array[$key], $resultArray, true)) {
70 ✔
7133
                    $resultArray[$key] = $array[$key];
70 ✔
7134
                }
7135

7136
                return $resultArray;
70 ✔
7137
            },
77 ✔
7138
            []
77 ✔
7139
        );
77 ✔
7140
        $this->generator = null;
77 ✔
7141

7142
        return $this;
77 ✔
7143
    }
7144

7145
    /**
7146
     * alias: for "Arrayy->uniqueNewIndex()"
7147
     *
7148
     * @return static
7149
     *                <p>(Mutable) Return this Arrayy object, with the appended values.</p>
7150
     *
7151
     * @see          Arrayy::unique()
7152
     *
7153
     * @phpstan-return static
7154
     */
7155
    public function unique(): self
7156
    {
7157
        return $this->uniqueNewIndex();
91 ✔
7158
    }
7159

7160
    /**
7161
     * Prepends one or more values to the beginning of array at once.
7162
     *
7163
     * @param mixed ...$args
7164
     *
7165
     * @return $this
7166
     *               <p>(Mutable) Return this Arrayy object, with prepended elements to the beginning of array.</p>
7167
     *
7168
     * @phpstan-param  array<TKey,T> ...$args
7169
     * @phpstan-return static
7170
     */
7171
    public function unshift(...$args): self
7172
    {
7173
        $this->generatorToArray();
42 ✔
7174

7175
        if (
7176
            $this->checkPropertyTypes
42 ✔
7177
            &&
7178
            $this->properties !== []
42 ✔
7179
        ) {
7180
            foreach ($args as $key => $value) {
14 ✔
7181
                $this->checkType($key, $value);
14 ✔
7182
            }
7183
        }
7184

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

7187
        return $this;
35 ✔
7188
    }
7189

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

7208
        return true;
7 ✔
7209
    }
7210

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

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

7275
        if ($this->array !== []) {
84 ✔
7276
            if ($recursive === true) {
70 ✔
7277
                if ($userData !== self::ARRAYY_HELPER_WALK) {
35 ✔
7278
                    \array_walk_recursive($this->array, $callable, $userData);
×
7279
                } else {
7280
                    \array_walk_recursive($this->array, $callable);
35 ✔
7281
                }
7282
            } else {
7283
                if ($userData !== self::ARRAYY_HELPER_WALK) {
35 ✔
7284
                    \array_walk($this->array, $callable, $userData);
×
7285
                } else {
7286
                    /* @phpstan-ignore argument.type */
7287
                    \array_walk($this->array, $callable);
35 ✔
7288
                }
7289
            }
7290
        }
7291

7292
        return $this;
84 ✔
7293
    }
7294

7295
    /**
7296
     * Returns a collection of matching items.
7297
     *
7298
     * @param string $keyOrPropertyOrMethod
7299
     *                                      <p>The property or method to evaluate.</p>
7300
     * @param mixed  $value
7301
     *                                      <p>The value to match.</p>
7302
     *
7303
     * @throws \InvalidArgumentException if property or method is not defined
7304
     *
7305
     * @return static
7306
     *
7307
     * @phpstan-return static
7308
     */
7309
    public function where(string $keyOrPropertyOrMethod, $value): self
7310
    {
7311
        return $this->filter(
14 ✔
7312
            function ($item) use ($keyOrPropertyOrMethod, $value) {
14 ✔
7313
                $accessorValue = $this->extractValue(
7 ✔
7314
                    $item, // @phpstan-ignore-line argument.type (filter() does not retain the item type in this callback)
7 ✔
7315
                    $keyOrPropertyOrMethod
7 ✔
7316
                );
7 ✔
7317

7318
                return $accessorValue === $value;
7 ✔
7319
            }
14 ✔
7320
        );
14 ✔
7321
    }
7322

7323
    /**
7324
     * Convert an array into an object.
7325
     *
7326
     * @param array $array
7327
     *
7328
     * @return \stdClass
7329
     *
7330
     * @phpstan-param array<int|string,mixed> $array
7331
     */
7332
    final protected static function arrayToObject(array $array = []): \stdClass
7333
    {
7334
        // init
7335
        $object = new \stdClass();
28 ✔
7336

7337
        if (\count($array, \COUNT_NORMAL) <= 0) {
28 ✔
7338
            return $object;
7 ✔
7339
        }
7340

7341
        foreach ($array as $name => $value) {
21 ✔
7342
            if (\is_array($value)) {
21 ✔
7343
                $object->{$name} = static::arrayToObject($value);
7 ✔
7344
            } else {
7345
                $object->{$name} = $value;
21 ✔
7346
            }
7347
        }
7348

7349
        return $object;
21 ✔
7350
    }
7351

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

7384
        if ($input === null) {
77 ✔
7385
            $input = $this->getGenerator();
28 ✔
7386
        }
7387

7388
        if ($search_values === null) {
77 ✔
7389
            foreach ($input as $key => $value) {
77 ✔
7390
                $keys[] = $key;
77 ✔
7391

7392
                // check if recursive is needed
7393
                if (\is_array($value)) {
77 ✔
7394
                    $keysTmp[] = $this->array_keys_recursive($value);
28 ✔
7395
                }
7396
            }
7397
        } else {
7398
            $is_array_tmp = \is_array($search_values);
7 ✔
7399

7400
            foreach ($input as $key => $value) {
7 ✔
7401
                if (
7402
                    (
7403
                        $is_array_tmp === false
7 ✔
7404
                        &&
7 ✔
7405
                        $strict === true
7 ✔
7406
                        &&
7 ✔
7407
                        $search_values === $value
7 ✔
7408
                    )
7409
                    ||
7410
                    (
7411
                        $is_array_tmp === false
7 ✔
7412
                        &&
7 ✔
7413
                        $strict === false
7 ✔
7414
                        &&
7 ✔
7415
                        $search_values == $value
7 ✔
7416
                    )
7417
                    ||
7418
                    (
7419
                        $is_array_tmp === true
7 ✔
7420
                        &&
7 ✔
7421
                        \in_array($value, $search_values, $strict)
7 ✔
7422
                    )
7423
                ) {
7424
                    $keys[] = $key;
7 ✔
7425
                }
7426

7427
                // check if recursive is needed
7428
                if (\is_array($value)) {
7 ✔
7429
                    $keysTmp[] = $this->array_keys_recursive($value);
7 ✔
7430
                }
7431
            }
7432
        }
7433

7434
        return $keysTmp === [] ? $keys : \array_merge($keys, ...$keysTmp);
77 ✔
7435
    }
7436

7437
    /**
7438
     * @param string     $path
7439
     * @param callable   $callable
7440
     * @param array|null $currentOffset
7441
     *
7442
     * @return void
7443
     *
7444
     * @phpstan-param array<array-key,mixed>|null $currentOffset
7445
     * @psalm-mutation-free
7446
     */
7447
    protected function callAtPath($path, $callable, &$currentOffset = null)
7448
    {
7449
        $this->generatorToArray();
77 ✔
7450

7451
        if ($currentOffset === null) {
77 ✔
7452
            $currentOffset = &$this->array;
77 ✔
7453
        }
7454

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

7457
        $nextPath = \array_shift($explodedPath);
77 ✔
7458
        if (!isset($currentOffset[$nextPath])) {
77 ✔
7459
            return;
7 ✔
7460
        }
7461

7462
        if ($explodedPath !== []) {
70 ✔
7463
            if (!\is_array($currentOffset[$nextPath])) {
7 ✔
NEW
7464
                return;
×
7465
            }
7466

7467
            $nestedOffset = &$currentOffset[$nextPath];
7 ✔
7468
            $this->callAtPath(
7 ✔
7469
                \implode($this->pathSeparator, $explodedPath),
7 ✔
7470
                $callable,
7 ✔
7471
                $nestedOffset
7 ✔
7472
            );
7 ✔
7473
        } else {
7474
            $callable($currentOffset[$nextPath]);
70 ✔
7475
        }
7476
    }
7477

7478
    /**
7479
     * Extracts the value of the given property or method from the object.
7480
     *
7481
     * @param array|object $object
7482
     *                                         <p>The object to extract the value from.</p>
7483
     * @param string    $keyOrPropertyOrMethod
7484
     *                                         <p>The property or method for which the
7485
     *                                         value should be extracted.</p>
7486
     *
7487
     * @throws \InvalidArgumentException if the method or property is not defined
7488
     *
7489
     * @return mixed
7490
     *               <p>The value extracted from the specified property or method.</p>
7491
     *
7492
     * @phpstan-param array<array-key,mixed>|object $object
7493
     */
7494
    final protected function extractValue($object, string $keyOrPropertyOrMethod)
7495
    {
7496
        if (\is_array($object)) {
14 ✔
7497
            if (\array_key_exists($keyOrPropertyOrMethod, $object)) {
7 ✔
7498
                return $object[$keyOrPropertyOrMethod];
7 ✔
7499
            }
7500
        } elseif ($object instanceof self && isset($object[$keyOrPropertyOrMethod])) {
14 ✔
7501
            $return = $object->get($keyOrPropertyOrMethod);
7 ✔
7502

7503
            if ($return instanceof self) {
7 ✔
7504
                return $return->toArray();
×
7505
            }
7506

7507
            return $return;
7 ✔
7508
        }
7509

7510
        if (\is_object($object) && \property_exists($object, $keyOrPropertyOrMethod)) {
7 ✔
7511
            return $object->{$keyOrPropertyOrMethod};
7 ✔
7512
        }
7513

NEW
7514
        if (\is_object($object) && \method_exists($object, $keyOrPropertyOrMethod)) {
×
7515
            return $object->{$keyOrPropertyOrMethod}();
×
7516
        }
7517

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

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

7544
        if ($data === null) {
9,196 ✔
7545
            throw new \InvalidArgumentException('Passed value should be a array');
14 ✔
7546
        }
7547

7548
        return $data;
9,182 ✔
7549
    }
7550

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

7568
            return true;
21 ✔
7569
        }
7570

7571
        return false;
8,496 ✔
7572
    }
7573

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

7587
            if ($direction === 'desc') {
70 ✔
7588
                $direction = \SORT_DESC;
14 ✔
7589
            } else {
7590
                $direction = \SORT_ASC;
63 ✔
7591
            }
7592
        }
7593

7594
        if (
7595
            $direction !== \SORT_DESC
301 ✔
7596
            &&
7597
            $direction !== \SORT_ASC
301 ✔
7598
        ) {
7599
            $direction = \SORT_ASC;
×
7600
        }
7601

7602
        return $direction;
301 ✔
7603
    }
7604

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

7616
        if (isset($PROPERTY_CACHE[$cacheKey])) {
523 ✔
7617
            $this->optionalProperties = $OPTIONAL_PROPERTY_CACHE[$cacheKey] ?? [];
467 ✔
7618

7619
            return $PROPERTY_CACHE[$cacheKey];
467 ✔
7620
        }
7621

7622
        $properties = $this->getPropertiesFromNativeDefinitions();
145 ✔
7623
        $optionalProperties = [];
145 ✔
7624
        $phpDocPropertyAnnotationStyle = null;
145 ✔
7625

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

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

7643
        $this->optionalProperties = $optionalProperties;
131 ✔
7644
        $OPTIONAL_PROPERTY_CACHE[$cacheKey] = $optionalProperties;
131 ✔
7645

7646
        return $PROPERTY_CACHE[$cacheKey] = $properties;
131 ✔
7647
    }
7648

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

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

7668
        $currentPhpDocPropertyAnnotationStyle = null;
138 ✔
7669
        if ($propertyTags !== []) {
138 ✔
7670
            $currentPhpDocPropertyAnnotationStyle = 'property';
49 ✔
7671
        } elseif ($arrayShapeItems !== []) {
138 ✔
7672
            $currentPhpDocPropertyAnnotationStyle = 'array-shape';
56 ✔
7673
        }
7674

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

7685
        if ($currentPhpDocPropertyAnnotationStyle !== null) {
138 ✔
7686
            $phpDocPropertyAnnotationStyle = $currentPhpDocPropertyAnnotationStyle;
98 ✔
7687
        }
7688

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

7706
        foreach ($arrayShapeItems as $item) {
138 ✔
7707
            $typeName = (string) $item->getKey();
56 ✔
7708
            if ($typeName === '') {
56 ✔
7709
                continue;
×
7710
            }
7711

7712
            $typeName = \trim($typeName, '\'"');
56 ✔
7713
            if (isset($properties[$typeName])) {
56 ✔
7714
                continue;
×
7715
            }
7716

7717
            $typeCheckPhpDoc = TypeCheckPhpDoc::fromDocTypeObject($typeName, $item->getValue());
56 ✔
7718
            $properties[$typeName] = $typeCheckPhpDoc;
56 ✔
7719
            if ($item->isOptional()) {
56 ✔
7720
                $optionalProperties[$typeName] = true;
35 ✔
7721
            }
7722
        }
7723
    }
7724

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

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

7753
        foreach ($docblock->getTagsByName('extends') as $tag) {
145 ✔
7754
            if (!$tag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Extends_) {
145 ✔
7755
                continue;
×
7756
            }
7757

7758
            $type = $tag->getType();
145 ✔
7759
            if (
7760
                !$type instanceof \phpDocumentor\Reflection\PseudoTypes\Generic
145 ✔
7761
                ||
7762
                !$this->isArrayyGenericTarget((string) $type->getFqsen())
145 ✔
7763
            ) {
7764
                continue;
131 ✔
7765
            }
7766

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

7776
        return $items;
145 ✔
7777
    }
7778

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

7793
        if (\in_array($fqcn, [self::class, ArrayyStrict::class], true)) {
145 ✔
7794
            return true;
138 ✔
7795
        }
7796

7797
        return \class_exists($fqcn) && \is_a($fqcn, self::class, true);
131 ✔
7798
    }
7799

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

7809
        do {
7810
            if ($reflector->getName() === self::class) {
145 ✔
7811
                break;
145 ✔
7812
            }
7813

7814
            foreach ($reflector->getProperties() as $property) {
145 ✔
7815
                if (
7816
                    $property->getDeclaringClass()->getName() !== $reflector->getName()
145 ✔
7817
                    ||
7818
                    $property->isStatic()
124 ✔
7819
                    ||
7820
                    isset($reservedProperties[$property->getName()])
124 ✔
7821
                    ||
7822
                    isset($properties[$property->getName()])
145 ✔
7823
                ) {
7824
                    continue;
145 ✔
7825
                }
7826

7827
                $properties[$property->getName()] = TypeCheckPhpDoc::fromReflectionProperty($property);
19 ✔
7828
            }
7829
        } while ($reflector = $reflector->getParentClass());
145 ✔
7830

7831
        return $properties;
145 ✔
7832
    }
7833

7834
    /**
7835
     * @return array<string, true>
7836
     */
7837
    private static function getReservedPropertyNames(): array
7838
    {
7839
        static $reservedProperties = null;
145 ✔
7840

7841
        if ($reservedProperties !== null) {
145 ✔
7842
            return $reservedProperties;
138 ✔
7843
        }
7844

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

7852
            $reservedProperties[$property->getName()] = true;
7 ✔
7853
        }
7854

7855
        return $reservedProperties;
7 ✔
7856
    }
7857

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

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

7882
            $pieces_count = \count($pieces, \COUNT_NORMAL);
259 ✔
7883
            $pieces_count_not_zero = $pieces_count > 0;
259 ✔
7884

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

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

7903
        return '';
56 ✔
7904
    }
7905

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

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

7949
            if ($returnTmp === true) {
99 ✔
7950
                return true;
71 ✔
7951
            }
7952
        }
7953

7954
        return false;
56 ✔
7955
    }
7956

7957
    /**
7958
     * @param mixed $data
7959
     *
7960
     * @return array<mixed>|null
7961
     */
7962
    protected function internalGetArray(&$data)
7963
    {
7964
        if (\is_array($data)) {
9,196 ✔
7965
            return $data;
9,154 ✔
7966
        }
7967

7968
        if (!$data) {
805 ✔
7969
            return [];
49 ✔
7970
        }
7971

7972
        if (\is_object($data) === true) {
798 ✔
7973
            if ($data instanceof \ArrayObject) {
749 ✔
7974
                return $data->getArrayCopy();
35 ✔
7975
            }
7976

7977
            if ($data instanceof \Generator) {
721 ✔
7978
                return static::createFromGeneratorImmutable($data)->toArray();
7 ✔
7979
            }
7980

7981
            if ($data instanceof \Traversable) {
714 ✔
7982
                return static::createFromObject($data)->toArray();
×
7983
            }
7984

7985
            if ($data instanceof \JsonSerializable) {
714 ✔
7986
                return (array) $data->jsonSerialize();
×
7987
            }
7988

7989
            if (\method_exists($data, '__toArray')) {
714 ✔
7990
                return (array) $data->__toArray();
×
7991
            }
7992

7993
            if (\method_exists($data, '__toString')) {
714 ✔
7994
                return [(string) $data];
×
7995
            }
7996
        }
7997

7998
        if (\is_callable($data)) {
763 ✔
7999
            /**
8000
             * @psalm-suppress InvalidPropertyAssignmentValue - why?
8001
             */
8002
            $this->generator = new ArrayyRewindableGenerator($data);
700 ✔
8003

8004
            return [];
700 ✔
8005
        }
8006

8007
        if (\is_scalar($data)) {
77 ✔
8008
            return [$data];
63 ✔
8009
        }
8010

8011
        return null;
14 ✔
8012
    }
8013

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

8025
        if (\is_float($key)) {
168 ✔
NEW
8026
            $key = (int) $key;
×
8027
        }
8028

8029
        if (
8030
            $this->pathSeparator
168 ✔
8031
            &&
8032
            (string) $key === $key
168 ✔
8033
            &&
8034
            \strpos($key, $this->pathSeparator) !== false
168 ✔
8035
        ) {
8036
            $path = \explode($this->pathSeparator, (string) $key);
14 ✔
8037
            $array = &$this->array;
14 ✔
8038

8039
            // crawl though the keys
8040
            while (\count($path, \COUNT_NORMAL) > 1) {
14 ✔
8041
                $key = \array_shift($path);
14 ✔
8042

8043
                if (!\is_array($array) || !\array_key_exists($key, $array)) {
14 ✔
8044
                    return false;
×
8045
                }
8046

8047
                $array = &$array[$key];
14 ✔
8048
            }
8049

8050
            $key = \array_shift($path);
14 ✔
8051

8052
            if (!\is_array($array)) {
14 ✔
8053
                return false;
7 ✔
8054
            }
8055

8056
            unset($array[$key]);
7 ✔
8057

8058
            return true;
7 ✔
8059
        }
8060

8061
        unset($this->array[$key]);
154 ✔
8062

8063
        return true;
154 ✔
8064
    }
8065

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

8091
        if ($key === null) {
8,055 ✔
8092
            return false;
×
8093
        }
8094

8095
        $this->generatorToArray();
8,055 ✔
8096

8097
        $array = &$this->array;
8,055 ✔
8098

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

8117
                $array = &$array[$key];
63 ✔
8118
            }
8119

8120
            $key = \array_shift($path);
63 ✔
8121
        }
8122

8123
        if ($array === null) {
8,055 ✔
8124
            $array = [];
28 ✔
8125
        } elseif (!\is_array($array)) {
8,034 ✔
8126
            throw new \RuntimeException('Can not set value at this path "' . $key . '" because (' . \gettype($array) . ')"' . \print_r($array, true) . '" is not an array.');
7 ✔
8127
        }
8128

8129
        $array[$key] = $value;
8,055 ✔
8130

8131
        return true;
8,055 ✔
8132
    }
8133

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

8149
        $object = \get_object_vars($object);
42 ✔
8150

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

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

8171
        if ($this->properties === []) {
9,182 ✔
8172
            if (
8173
                $this->checkPropertyTypes === true
8,489 ✔
8174
                ||
8175
                $checkPropertiesInConstructor === true
8,489 ✔
8176
            ) {
8177
                $this->properties = $this->getPropertiesFromPhpDoc();
495 ✔
8178
            }
8179

8180
            /** @var TypeCheckInterface[] $properties */
8181
            $properties = $this->properties;
8,475 ✔
8182
            $requiredProperties = \array_diff_key($properties, $this->optionalProperties);
8,475 ✔
8183

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

8195
        foreach ($data as $key => &$valueInner) {
9,154 ✔
8196
            $this->internalSet(
7,971 ✔
8197
                $key,
7,971 ✔
8198
                $valueInner,
7,971 ✔
8199
                $checkPropertiesInConstructor
7,971 ✔
8200
            );
7,971 ✔
8201
        }
8202
    }
8203

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

8225
        switch ($direction) {
8226
            case 'desc':
126 ✔
8227
            case \SORT_DESC:
8228
                \krsort($elements, $strategy);
42 ✔
8229

8230
                break;
42 ✔
8231
            case 'asc':
91 ✔
8232
            case \SORT_ASC:
91 ✔
8233
            default:
8234
                \ksort($elements, $strategy);
91 ✔
8235
        }
8236

8237
        return $this;
126 ✔
8238
    }
8239

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

8261
        if (!$strategy) {
168 ✔
8262
            $strategy = \SORT_REGULAR;
168 ✔
8263
        }
8264

8265
        switch ($direction) {
8266
            case 'desc':
168 ✔
8267
            case \SORT_DESC:
8268
                if ($keepKeys) {
91 ✔
8269
                    \arsort($elements, $strategy);
63 ✔
8270
                } else {
8271
                    \rsort($elements, $strategy);
28 ✔
8272
                }
8273

8274
                break;
91 ✔
8275
            case 'asc':
77 ✔
8276
            case \SORT_ASC:
77 ✔
8277
            default:
8278
                if ($keepKeys) {
77 ✔
8279
                    \asort($elements, $strategy);
28 ✔
8280
                } else {
8281
                    \sort($elements, $strategy);
49 ✔
8282
                }
8283
        }
8284

8285
        return $this;
168 ✔
8286
    }
8287

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

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

8318
        return $array;
175 ✔
8319
    }
8320

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

8339
        if (isset($this->properties[self::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES])) {
1,190 ✔
8340
            $this->properties[self::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES]->checkType($value);
833 ✔
8341
        } elseif ($key !== null && isset($this->properties[$key])) {
434 ✔
8342
            $this->properties[$key]->checkType($value);
434 ✔
8343
        }
8344
    }
8345
}
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