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

voku / Arrayy / 38691941449

15 Oct 2026 06:21AM UTC coverage: 92.433% (-0.3%) from 92.733%
38691941449

Pull #193

github

web-flow
Merge f30be5f62 into de7df893f
Pull Request #193: docs(types): expose generic Arrayy returns to IDEs with portable PHPDoc

2773 of 3000 relevant lines covered (92.43%)

36.59 hits per line

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

92.27
/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<array-key, mixed> = 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);
1,340 ✔
129

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

136
        $this->setInitialValuesAndProperties($data, $checkPropertiesInConstructor);
1,338 ✔
137

138
        $this->setIteratorClass($iteratorClass);
1,305 ✔
139
    }
140

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

150
        if ($this->generator !== null) {
57 ✔
151
            $this->generator = clone $this->generator;
1 ✔
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) {
1 ✔
168
            $this->generatorToArray();
1 ✔
169

170
            return $this->array[$key] ?? false;
1 ✔
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);
1 ✔
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);
2 ✔
208
    }
209

210
    /**
211
     * magic to string
212
     *
213
     * @return string
214
     */
215
    public function __toString(): string
216
    {
217
        return $this->toString();
15 ✔
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);
169 ✔
247

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

256
        return $return;
169 ✔
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) {
14 ✔
277
            $get = $this->get($key);
5 ✔
278
            if ($get !== null) {
5 ✔
279
                $value = \array_merge_recursive(
1 ✔
280
                    !$get instanceof self ? [$get] : $get->getArray(),
1 ✔
281
                    !\is_array($value) ? [$value] : $value
1 ✔
282
                );
1 ✔
283
            }
284

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

288
            return $this;
4 ✔
289
        }
290

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

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

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

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

334
        return $this;
20 ✔
335
    }
336

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

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

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

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

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

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

404
        return $this;
4 ✔
405
    }
406

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

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

431
        return $that;
4 ✔
432
    }
433

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

574
        return $return;
×
575
    }
576

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

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

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

610
        return $this;
4 ✔
611
    }
612

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

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

636
        return $that;
4 ✔
637
    }
638

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

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

654
        return $this;
8 ✔
655
    }
656

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

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

675
        return $that;
4 ✔
676
    }
677

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

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

693
        return $this;
10 ✔
694
    }
695

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

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

714
        return $that;
4 ✔
715
    }
716

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

735
        $offsetExists = $this->keyExists($offset);
199 ✔
736
        if ($offsetExists === true) {
199 ✔
737
            return true;
178 ✔
738
        }
739

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

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

770
        return $offsetExists;
139 ✔
771
    }
772

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

791
        if ($this->offsetExists($offset)) {
168 ✔
792
            $value = &$this->__get($offset);
166 ✔
793
        }
794

795
        return $value;
168 ✔
796
    }
797

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

811
        if ($offset === null) {
43 ✔
812
            if ($this->properties !== []) {
9 ✔
813
                $this->checkType(null, $value);
4 ✔
814
            }
815

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

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

839
        if ($this->array === []) {
26 ✔
840
            return;
6 ✔
841
        }
842

843
        if ($this->keyExists($offset)) {
21 ✔
844
            unset($this->array[$offset]);
14 ✔
845

846
            return;
14 ✔
847
        }
848

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

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

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

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

897
        return \serialize($this);
1 ✔
898
    }
899

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

917
            return;
1,305 ✔
918
        }
919

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

929
                return;
×
930
            }
931
        }
932

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

936
    /**
937
     * Sort the entries with a user-defined comparison function and maintain key association.
938
     *
939
     * @param callable $callable
940
     *
941
     *@throws \InvalidArgumentException
942
     *
943
     * @return $this
944
     *               <p>(Mutable) Return this Arrayy object.</p>
945
     *
946
     * @phpstan-param  callable(T,T):int $callable
947
     * @phpstan-return static
948
     */
949
    #[\ReturnTypeWillChange]
950
    public function uasort($callable): self
951
    {
952
        // Preserve the public exception contract for invalid callbacks.
953
        if (!\is_callable($callable)) { // @phpstan-ignore function.alreadyNarrowedType
15 ✔
954
            throw new \InvalidArgumentException('Passed function must be callable');
6 ✔
955
        }
956

957
        $this->generatorToArray();
9 ✔
958

959
        \uasort($this->array, $callable);
9 ✔
960

961
        return $this;
9 ✔
962
    }
963

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

982
        /**
983
         * @psalm-suppress ImpureMethodCall - object is already cloned
984
         */
985
        $that->uasort($callable);
8 ✔
986

987
        return $that;
5 ✔
988
    }
989

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

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

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

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

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

1090
        return $this;
1 ✔
1091
    }
1092

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

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

1121
        return self::create(
10 ✔
1122
            $result,
10 ✔
1123
            $this->iteratorClass,
10 ✔
1124
            false
10 ✔
1125
        );
10 ✔
1126
    }
1127

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

1144
        foreach ($this->getGenerator() as $key => $item) {
10 ✔
1145
            if ($item instanceof self) {
9 ✔
1146
                $result[$key] = $item->appendToEachValue($prefix);
×
1147
            } elseif (\is_array($item)) {
9 ✔
1148
                $result[$key] = self::create($item, $this->iteratorClass, false)->appendToEachValue($prefix)->toArray();
×
1149
            } elseif (\is_object($item) === true) {
9 ✔
1150
                $result[$key] = $item;
1 ✔
1151
            } else {
1152
                $result[$key] = $prefix . $item;
8 ✔
1153
            }
1154
        }
1155

1156
        return self::create($result, $this->iteratorClass, false);
10 ✔
1157
    }
1158

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

1171
        \arsort($this->array);
4 ✔
1172

1173
        return $this;
4 ✔
1174
    }
1175

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

1189
        $that->generatorToArray();
10 ✔
1190

1191
        \arsort($that->array);
10 ✔
1192

1193
        return $that;
10 ✔
1194
    }
1195

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

1220
        foreach ($that->getGenerator() as $key => $value) {
3 ✔
1221
            $closure($value, $key);
3 ✔
1222
        }
1223

1224
        return static::create(
3 ✔
1225
            $that->toArray(),
3 ✔
1226
            $this->iteratorClass,
3 ✔
1227
            false
3 ✔
1228
        );
3 ✔
1229
    }
1230

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

1249
        if (!$count) {
10 ✔
1250
            return 0;
2 ✔
1251
        }
1252

1253
        if ((int) $decimals !== $decimals) {
8 ✔
1254
            $decimals = 0;
3 ✔
1255
        }
1256

1257
        $sum = 0;
8 ✔
1258
        foreach ($array as $value) {
8 ✔
1259
            if (
1260
                \is_int($value)
8 ✔
1261
                ||
1262
                \is_float($value)
7 ✔
1263
                ||
1264
                \is_bool($value)
8 ✔
1265
            ) {
1266
                $sum += $value;
6 ✔
1267
            } elseif (\is_string($value) && \is_numeric($value)) {
2 ✔
1268
                $sum += (float) $value;
×
1269
            }
1270
        }
1271

1272
        return \round($sum / $count, $decimals);
8 ✔
1273
    }
1274

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

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

1313
            $return[$key] = $value;
6 ✔
1314
        }
1315

1316
        return static::create(
6 ✔
1317
            $return,
6 ✔
1318
            $this->iteratorClass,
6 ✔
1319
            false
6 ✔
1320
        );
6 ✔
1321
    }
1322

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

1339
        return $this;
11 ✔
1340
    }
1341

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

1367
                    $values[$key] = $value;
×
1368
                    if ($tmpCounter === $size) {
×
1369
                        yield $values;
×
1370

1371
                        $values = [];
×
1372
                        $tmpCounter = 0;
×
1373
                    }
1374
                }
1375

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

1387
                    $values[] = $value;
6 ✔
1388
                    if ($tmpCounter === $size) {
6 ✔
1389
                        yield $values;
6 ✔
1390

1391
                        $values = [];
6 ✔
1392
                        $tmpCounter = 0;
6 ✔
1393
                    }
1394
                }
1395

1396
                if ($values !== []) {
6 ✔
1397
                    yield $values;
5 ✔
1398
                }
1399
            };
6 ✔
1400
        }
1401

1402
        return static::create(
6 ✔
1403
            $generator,
6 ✔
1404
            $this->iteratorClass,
6 ✔
1405
            false
6 ✔
1406
        );
6 ✔
1407
    }
1408

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

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

1456
            return $this;
3 ✔
1457
        }
1458

1459
        $this->array = [];
7 ✔
1460
        $this->generator = null;
7 ✔
1461

1462
        return $this;
7 ✔
1463
    }
1464

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

1485
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1486
        $tmpCount = 0;
10 ✔
1487
        foreach ($this->getGeneratorByReference() as &$valueFromArray) {
10 ✔
1488
            $tmpCount++;
8 ✔
1489

1490
            if ($strict) {
8 ✔
1491
                if ($value !== $valueFromArray) {
8 ✔
1492
                    return false;
3 ✔
1493
                }
1494
            } else {
1495
                /** @noinspection NestedPositiveIfStatementsInspection */
1496
                if ($value != $valueFromArray) {
×
1497
                    return false;
×
1498
                }
1499
            }
1500
        }
1501

1502
        return $tmpCount !== 0;
7 ✔
1503
    }
1504

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

1525
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1526
        foreach ($this->getGeneratorByReference() as &$valueFromArray) {
14 ✔
1527
            if ($strict) {
11 ✔
1528
                if ($value === $valueFromArray) {
11 ✔
1529
                    return true;
8 ✔
1530
                }
1531
            } else {
1532
                /** @noinspection NestedPositiveIfStatementsInspection */
1533
                if ($value == $valueFromArray) {
×
1534
                    return true;
×
1535
                }
1536
            }
1537
        }
1538

1539
        return false;
7 ✔
1540
    }
1541

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

1563
        if ($recursive === true) {
24 ✔
1564
            /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1565
            foreach ($this->getGeneratorByReference() as &$valueTmp) {
24 ✔
1566
                if (\is_array($valueTmp)) {
22 ✔
1567
                    $return = (new self($valueTmp))->containsCaseInsensitive($value, $recursive);
5 ✔
1568
                    if ($return === true) {
5 ✔
1569
                        return $return;
3 ✔
1570
                    }
1571
                } elseif (\mb_strtoupper((string) $valueTmp) === \mb_strtoupper((string) $value)) {
22 ✔
1572
                    return true;
16 ✔
1573
                }
1574
            }
1575

1576
            return false;
8 ✔
1577
        }
1578

1579
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
1580
        foreach ($this->getGeneratorByReference() as &$valueTmp) {
12 ✔
1581
            if (\mb_strtoupper((string) $valueTmp) === \mb_strtoupper((string) $value)) {
11 ✔
1582
                return true;
8 ✔
1583
            }
1584
        }
1585

1586
        return false;
4 ✔
1587
    }
1588

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

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

1642
        return \count(
1 ✔
1643
            \array_intersect($needles, $this->keys()->toArray()),
1 ✔
1644
            \COUNT_NORMAL
1 ✔
1645
        )
1 ✔
1646
                ===
1 ✔
1647
                \count(
1 ✔
1648
                    $needles,
1 ✔
1649
                    \COUNT_NORMAL
1 ✔
1650
                );
1 ✔
1651
    }
1652

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

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

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

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

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

1750
        return $return;
7 ✔
1751
    }
1752

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

1780
        return $instance;
799 ✔
1781
    }
1782

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

1807
        if ($items === null) {
2 ✔
1808
            $items = $this->getArray();
2 ✔
1809
        }
1810

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

1819
        if (\count($flatten) === 0) {
2 ✔
1820
            return [];
×
1821
        }
1822

1823
        return \array_merge_recursive([], ...$flatten);
2 ✔
1824
    }
1825

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

1844
        return $this;
30 ✔
1845
    }
1846

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

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

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

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

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

1931
        if ($object instanceof self) {
4 ✔
1932
            $objectArray = $object->getGenerator();
4 ✔
1933
        } else {
1934
            $objectArray = $object;
×
1935
        }
1936

1937
        foreach ($objectArray as $key => $value) {
4 ✔
1938
            /**
1939
             * @psalm-suppress ImpureMethodCall - object is already re-created
1940
             */
1941
            $arrayy->internalSet($key, $value);
3 ✔
1942
        }
1943

1944
        return $arrayy;
4 ✔
1945
    }
1946

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

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

1982
            if (!empty($array)) {
1 ✔
1983
                $array = $array[0];
1 ✔
1984
            }
1985
        } else {
1986
            /** @noinspection NestedPositiveIfStatementsInspection */
1987
            if ($delimiter !== null) {
9 ✔
1988
                $array = \explode($delimiter, $str);
7 ✔
1989
            } else {
1990
                $array = [$str];
2 ✔
1991
            }
1992
        }
1993

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

2007
        /** @var static $return - help for phpstan */
2008
        $return = static::create($array);
10 ✔
2009

2010
        return $return;
10 ✔
2011
    }
2012

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

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

2051
        return $return;
2 ✔
2052
    }
2053

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

2067
        return \current($this->array);
×
2068
    }
2069

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

2100
        /* @phpstan-ignore argument.type (internal keys are array-key|TKey, the callback contract is TKey) */
2101
        \uksort($this->array, $callable);
5 ✔
2102

2103
        return $this;
5 ✔
2104
    }
2105

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

2126
        $that->generatorToArray();
1 ✔
2127

2128
        /**
2129
         * @psalm-suppress ImpureFunctionCall - object is already cloned
2130
         */
2131
        /* @phpstan-ignore argument.type (internal keys are array-key|TKey, the callback contract is TKey) */
2132
        \uksort($that->array, $callable);
1 ✔
2133

2134
        return $that;
1 ✔
2135
    }
2136

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

2165
        \usort($this->array, $callable);
11 ✔
2166

2167
        return $this;
11 ✔
2168
    }
2169

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

2190
        /**
2191
         * @psalm-suppress ImpureMethodCall - object is already cloned
2192
         */
2193
        $that->customSortValues($callable);
5 ✔
2194

2195
        return $that;
5 ✔
2196
    }
2197

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

2209
        foreach ($keyOrKeys as $key) {
9 ✔
2210
            $this->offsetUnset($key);
9 ✔
2211
        }
2212
    }
2213

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

2238
        $generator = function () use ($array): \Generator {
13 ✔
2239
            foreach ($this->getGenerator() as $key => $value) {
13 ✔
2240
                if (\in_array($value, $array, true) === false) {
11 ✔
2241
                    yield $key => $value;
5 ✔
2242
                }
2243
            }
2244
        };
13 ✔
2245

2246
        return static::create(
13 ✔
2247
            $generator,
13 ✔
2248
            $this->iteratorClass,
13 ✔
2249
            false
13 ✔
2250
        );
13 ✔
2251
    }
2252

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

2273
        $generator = function () use ($array): \Generator {
9 ✔
2274
            foreach ($this->getGenerator() as $key => $value) {
9 ✔
2275
                if (\array_key_exists($key, $array) === false) {
8 ✔
2276
                    yield $key => $value;
2 ✔
2277
                }
2278
            }
2279
        };
9 ✔
2280

2281
        return static::create(
9 ✔
2282
            $generator,
9 ✔
2283
            $this->iteratorClass,
9 ✔
2284
            false
9 ✔
2285
        );
9 ✔
2286
    }
2287

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

2308
        $generator = function () use ($array): \Generator {
9 ✔
2309
            foreach ($this->getGenerator() as $key => $value) {
9 ✔
2310
                $isset = isset($array[$key]);
8 ✔
2311

2312
                if (
2313
                    !$isset
8 ✔
2314
                    ||
2315
                    $array[$key] !== $value
8 ✔
2316
                ) {
2317
                    yield $key => $value;
4 ✔
2318
                }
2319
            }
2320
        };
9 ✔
2321

2322
        return static::create(
9 ✔
2323
            $generator,
9 ✔
2324
            $this->iteratorClass,
9 ✔
2325
            false
9 ✔
2326
        );
9 ✔
2327
    }
2328

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

2352
        if (
2353
            $helperVariableForRecursion !== null
1 ✔
2354
            &&
2355
            \is_array($helperVariableForRecursion)
1 ✔
2356
        ) {
2357
            $arrayForTheLoop = $helperVariableForRecursion;
×
2358
        } else {
2359
            $arrayForTheLoop = $this->getGenerator();
1 ✔
2360
        }
2361

2362
        foreach ($arrayForTheLoop as $key => $value) {
1 ✔
2363
            if ($value instanceof self) {
1 ✔
2364
                $value = $value->toArray();
1 ✔
2365
            }
2366

2367
            if (\array_key_exists($key, $array)) {
1 ✔
2368
                if ($value !== $array[$key]) {
1 ✔
2369
                    $result[$key] = $value;
1 ✔
2370
                }
2371
            } else {
2372
                $result[$key] = $value;
1 ✔
2373
            }
2374
        }
2375

2376
        return static::create(
1 ✔
2377
            $result,
1 ✔
2378
            $this->iteratorClass,
1 ✔
2379
            false
1 ✔
2380
        );
1 ✔
2381
    }
2382

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

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

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

2458
        foreach ($this->getGenerator() as $key => $value) {
6 ✔
2459
            $array[$key] = $closure($value, $key);
6 ✔
2460
        }
2461

2462
        return static::create(
6 ✔
2463
            $array,
6 ✔
2464
            $this->iteratorClass,
6 ✔
2465
            false
6 ✔
2466
        );
6 ✔
2467
    }
2468

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

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

2493
        return \end($this->array);
×
2494
    }
2495

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

2518
        foreach ($this->getGenerator() as $key => $value) {
4 ✔
2519
            if ($closure($value, $key)) {
3 ✔
2520
                $isExists = true;
1 ✔
2521

2522
                break;
1 ✔
2523
            }
2524
        }
2525

2526
        return $isExists;
4 ✔
2527
    }
2528

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

2552
        $this->generatorToArray();
7 ✔
2553

2554
        $tmpArray = $this->array;
7 ✔
2555

2556
        $count = \count($tmpArray);
7 ✔
2557

2558
        while ($count < $num) {
7 ✔
2559
            $tmpArray[] = $default;
4 ✔
2560
            ++$count;
4 ✔
2561
        }
2562

2563
        return static::create(
7 ✔
2564
            $tmpArray,
7 ✔
2565
            $this->iteratorClass,
7 ✔
2566
            false
7 ✔
2567
        );
7 ✔
2568
    }
2569

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

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

2629
            $generator = function () use ($closure) {
16 ✔
2630
                foreach ($this->getGenerator() as $key => $value) {
15 ✔
2631
                    if ($closure($value, $key) === true) {
14 ✔
2632
                        yield $key => $value;
11 ✔
2633
                    }
2634
                }
2635
            };
16 ✔
2636
        } else {
2637
            $generator = function () use ($closure) {
1 ✔
2638
                foreach ($this->getGenerator() as $key => $value) {
1 ✔
2639
                    if ($closure($value) === true) {
1 ✔
2640
                        yield $key => $value;
1 ✔
2641
                    }
2642
                }
2643
            };
1 ✔
2644
        }
2645

2646
        return static::create(
16 ✔
2647
            $generator,
16 ✔
2648
            $this->iteratorClass,
16 ✔
2649
            false
16 ✔
2650
        );
16 ✔
2651
    }
2652

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

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

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

2744
                    return $ops[$comparisonOp]($item, $property, $value);
1 ✔
2745
                }
1 ✔
2746
            )
1 ✔
2747
        );
1 ✔
2748

2749
        return static::create(
1 ✔
2750
            $result,
1 ✔
2751
            $this->iteratorClass,
1 ✔
2752
            false
1 ✔
2753
        );
1 ✔
2754
    }
2755

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

2783
        return false;
3 ✔
2784
    }
2785

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

2813
        return false;
4 ✔
2814
    }
2815

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

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

2863
        return $this->get($key_first);
22 ✔
2864
    }
2865

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

2880
        /** @phpstan-var TKey|null $return - help for phpstan */
2881
        $return = \array_key_first($this->array);
32 ✔
2882

2883
        return $return;
32 ✔
2884
    }
2885

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

2906
        if ($number === null) {
37 ✔
2907
            $array = (array) \array_shift($arrayTmp);
14 ✔
2908
        } else {
2909
            $array = \array_splice($arrayTmp, 0, $number);
23 ✔
2910
        }
2911

2912
        return static::create(
37 ✔
2913
            $array,
37 ✔
2914
            $this->iteratorClass,
37 ✔
2915
            false
37 ✔
2916
        );
37 ✔
2917
    }
2918

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

2935
        if ($number === null) {
3 ✔
2936
            $array = (array) \array_shift($arrayTmp);
×
2937
        } else {
2938
            $array = \array_splice($arrayTmp, 0, $number);
3 ✔
2939
        }
2940

2941
        return static::create(
3 ✔
2942
            $array,
3 ✔
2943
            $this->iteratorClass,
3 ✔
2944
            false
3 ✔
2945
        );
3 ✔
2946
    }
2947

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

2967
        if ($number === null) {
34 ✔
2968
            $shift = \array_shift($this->array);
19 ✔
2969
            $this->array = $shift !== null ? [$shift] : [];
19 ✔
2970
        } else {
2971
            $splice = \array_splice($this->array, 0, $number);
15 ✔
2972
            $this->array = $splice;
15 ✔
2973
        }
2974

2975
        return $this;
34 ✔
2976
    }
2977

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

2999
        return static::create(
1 ✔
3000
            $generator,
1 ✔
3001
            $this->iteratorClass,
1 ✔
3002
            false
1 ✔
3003
        );
1 ✔
3004
    }
3005

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

3047
            return clone $this;
1 ✔
3048
        }
3049

3050
        if ($array !== null) {
286 ✔
3051
            if ($useByReference) {
4 ✔
3052
                $usedArray = &$array;
×
3053
            } else {
3054
                $usedArray = $array;
4 ✔
3055
            }
3056
        } else {
3057
            $this->generatorToArray();
283 ✔
3058

3059
            if ($useByReference) {
283 ✔
3060
                $usedArray = &$this->array;
169 ✔
3061
            } else {
3062
                $usedArray = $this->array;
134 ✔
3063
            }
3064
        }
3065

3066
        if ($key === null) {
286 ✔
3067
            return static::create(
1 ✔
3068
                [],
1 ✔
3069
                $this->iteratorClass,
1 ✔
3070
                false
1 ✔
3071
            )->createByReference($usedArray);
1 ✔
3072
        }
3073

3074
        // php cast "bool"-index into "int"-index
3075
        /* @phpstan-ignore identical.alwaysFalse */
3076
        if ((bool) $key === $key) {
286 ✔
3077
            $key = (int) $key;
×
3078
        }
3079

3080
        if (\array_key_exists($key, $usedArray) === true) {
286 ✔
3081
            if (\is_array($usedArray[$key])) {
248 ✔
3082
                return static::create(
22 ✔
3083
                    [],
22 ✔
3084
                    $this->iteratorClass,
22 ✔
3085
                    false
22 ✔
3086
                )->createByReference($usedArray[$key]);
22 ✔
3087
            }
3088

3089
            return $usedArray[$key];
231 ✔
3090
        }
3091

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

3105
            foreach ($segments as $segment) {
31 ✔
3106
                if (
3107
                    (
3108
                        \is_array($usedArrayTmp)
31 ✔
3109
                        ||
31 ✔
3110
                        $usedArrayTmp instanceof \ArrayAccess
31 ✔
3111
                    )
3112
                    &&
3113
                    isset($usedArrayTmp[$segment])
31 ✔
3114
                ) {
3115
                    $usedArrayTmp = $usedArrayTmp[$segment];
30 ✔
3116

3117
                    continue;
30 ✔
3118
                }
3119

3120
                if (
3121
                    \is_object($usedArrayTmp) === true
14 ✔
3122
                    &&
3123
                    \property_exists($usedArrayTmp, $segment)
14 ✔
3124
                ) {
3125
                    $usedArrayTmp = $usedArrayTmp->{$segment};
1 ✔
3126

3127
                    continue;
1 ✔
3128
                }
3129

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

3143
                            continue;
×
3144
                        }
3145

3146
                        if (
3147
                            (
3148
                                \is_array($dataTmp)
1 ✔
3149
                                ||
1 ✔
3150
                                $dataTmp instanceof \ArrayAccess
1 ✔
3151
                            )
3152
                            &&
3153
                            isset($dataTmp[$keyTmp])
1 ✔
3154
                        ) {
3155
                            $returnTmp->add($dataTmp[$keyTmp]);
×
3156

3157
                            continue;
×
3158
                        }
3159

3160
                        if (
3161
                            \is_object($dataTmp) === true
1 ✔
3162
                            &&
3163
                            \property_exists($dataTmp, $keyTmp)
1 ✔
3164
                        ) {
3165
                            $returnTmp->add($dataTmp->{$keyTmp});
1 ✔
3166

3167
                            continue;
1 ✔
3168
                        }
3169
                    }
3170

3171
                    if ($returnTmp->count() > 0) {
1 ✔
3172
                        return $returnTmp;
1 ✔
3173
                    }
3174
                }
3175

3176
                return $fallback instanceof \Closure ? $fallback() : $fallback;
12 ✔
3177
            }
3178

3179
            if (\is_array($usedArrayTmp)) {
28 ✔
3180
                return static::create(
6 ✔
3181
                    [],
6 ✔
3182
                    $this->iteratorClass,
6 ✔
3183
                    false
6 ✔
3184
                )->createByReference($usedArrayTmp);
6 ✔
3185
            }
3186

3187
            return $usedArrayTmp;
28 ✔
3188
        }
3189

3190
        if (!isset($usedArray[$key])) {
30 ✔
3191
            return $fallback instanceof \Closure ? $fallback() : $fallback;
30 ✔
3192
        }
3193

3194
        return static::create(
×
3195
            [],
×
3196
            $this->iteratorClass,
×
3197
            false
×
3198
        )->createByReference($usedArray);
×
3199
    }
3200

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

3215
        return $return;
15 ✔
3216
    }
3217

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

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

3266
        $jsonObject = \json_decode($json, false);
8 ✔
3267

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

3275
        /** @var static $return - hack for phpstan */
3276
        $return = $mapper->map($jsonObject, $class);
8 ✔
3277

3278
        return $return;
5 ✔
3279
    }
3280

3281
    /**
3282
     * @return array<array-key,TypeCheckInterface>|TypeCheckArray<array-key,TypeCheckInterface>
3283
     *
3284
     * @internal
3285
     */
3286
    public function getPhpDocPropertiesFromClass()
3287
    {
3288
        if ($this->properties === []) {
24 ✔
3289
            $this->properties = $this->getPropertiesFromPhpDoc();
14 ✔
3290
        }
3291

3292
        return $this->properties;
24 ✔
3293
    }
3294

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

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

3352
                    if ($indexKey !== null) {
1 ✔
3353
                        foreach ($value as $keyInner => $valueInner) {
1 ✔
3354
                            if ($indexKey === $keyInner) {
1 ✔
3355
                                $newKey = $valueInner;
1 ✔
3356
                            }
3357

3358
                            if ($columnKey === $keyInner) {
1 ✔
3359
                                $newValue = $valueInner;
1 ✔
3360
                                $newValueFound = true;
1 ✔
3361
                            }
3362
                        }
3363
                    } else {
3364
                        foreach ($value as $keyInner => $valueInner) {
1 ✔
3365
                            if ($columnKey === $keyInner) {
1 ✔
3366
                                $newValue = $valueInner;
1 ✔
3367
                                $newValueFound = true;
1 ✔
3368
                            }
3369
                        }
3370
                    }
3371

3372
                    if ($newValueFound === false) {
1 ✔
3373
                        if ($newKey !== null) {
1 ✔
3374
                            yield $newKey => $value;
1 ✔
3375
                        } else {
3376
                            yield $value;
1 ✔
3377
                        }
3378
                    } else {
3379
                        /** @noinspection NestedPositiveIfStatementsInspection */
3380
                        if ($newKey !== null) {
1 ✔
3381
                            yield $newKey => $newValue;
1 ✔
3382
                        } else {
3383
                            yield $newValue;
1 ✔
3384
                        }
3385
                    }
3386
                }
3387
            };
1 ✔
3388
        }
3389

3390
        return static::create(
1 ✔
3391
            $generator,
1 ✔
3392
            $this->iteratorClass,
1 ✔
3393
            false
1 ✔
3394
        );
1 ✔
3395
    }
3396

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

3411
            return;
5 ✔
3412
        }
3413

3414
        // -> false-positive -> see "&$value"
3415
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
3416
        foreach ($this->array as $key => &$value) {
70 ✔
3417
            yield $key => $value;
63 ✔
3418
        }
3419
    }
3420

3421
    /**
3422
     * Get the current array from the "Arrayy"-object as generator.
3423
     *
3424
     * @return \Generator<mixed,T>|\Generator<TKey,T>
3425
     *
3426
     * @phpstan-return \Generator<mixed,T>|\Generator<TKey,T>
3427
     * @psalm-mutation-free
3428
     */
3429
    public function getGenerator(): \Generator
3430
    {
3431
        if ($this->generator instanceof ArrayyRewindableGenerator) {
1,148 ✔
3432
            yield from $this->generator;
82 ✔
3433

3434
            return;
80 ✔
3435
        }
3436

3437
        yield from $this->array;
1,146 ✔
3438
    }
3439

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

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

3469
    /**
3470
     * Get the current array from the "Arrayy"-object as object.
3471
     *
3472
     * @return \stdClass
3473
     */
3474
    public function getObject(): \stdClass
3475
    {
3476
        return self::arrayToObject($this->toArray());
4 ✔
3477
    }
3478

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

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

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

3526
    /**
3527
     * alias: for "Arrayy->randomValue()"
3528
     *
3529
     * @return T|static|null
3530
     *                    <p>Get a random value or null if there wasn't a value.</p>
3531
     *
3532
     * @phpstan-return (T is array ? static : T)|null
3533
     *
3534
     * @see Arrayy::randomValue()
3535
     */
3536
    public function getRandomValue()
3537
    {
3538
        return $this->randomValue();
4 ✔
3539
    }
3540

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

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

3571
        return static::create(
4 ✔
3572
            \array_values($this->array),
4 ✔
3573
            $this->iteratorClass,
4 ✔
3574
            false
4 ✔
3575
        );
4 ✔
3576
    }
3577

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

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

3610
        // Iterate over values, group by property/results from closure.
3611
        foreach ($this->getGenerator() as $key => $value) {
4 ✔
3612
            if (\is_callable($grouper) === true) {
4 ✔
3613
                $groupKey = $grouper($value, $key);
3 ✔
3614
            } else {
3615
                $groupKey = $this->get($grouper);
1 ✔
3616
            }
3617

3618
            $newValue = $this->get($groupKey, null, $result);
4 ✔
3619

3620
            if ($groupKey instanceof self) {
4 ✔
3621
                $groupKey = $groupKey->toArray();
×
3622
            }
3623

3624
            if ($newValue instanceof self) {
4 ✔
3625
                $newValue = $newValue->toArray();
4 ✔
3626
            }
3627

3628
            // Add to result.
3629
            if ($groupKey !== null) {
4 ✔
3630
                $result[$groupKey] = $newValue;
3 ✔
3631

3632
                if ($saveKeys) {
3 ✔
3633
                    $result[$groupKey][$key] = $value;
2 ✔
3634
                } else {
3635
                    $result[$groupKey][] = $value;
1 ✔
3636
                }
3637
            }
3638
        }
3639

3640
        return static::create(
4 ✔
3641
            $result,
4 ✔
3642
            $this->iteratorClass,
4 ✔
3643
            false
4 ✔
3644
        );
4 ✔
3645
    }
3646

3647
    /**
3648
     * Check if an array has a given key.
3649
     *
3650
     * @param mixed $key
3651
     *
3652
     * @return bool
3653
     *
3654
     * @phpstan-param null|array-key|array<array-key> $key
3655
     */
3656
    public function has($key): bool
3657
    {
3658
        static $UN_FOUND = null;
30 ✔
3659

3660
        if ($UN_FOUND === null) {
30 ✔
3661
            // Generate unique string to use as marker.
3662
            $UN_FOUND = 'arrayy--' . \uniqid('arrayy', true);
1 ✔
3663
        }
3664

3665
        if (\is_array($key)) {
30 ✔
3666
            if ($key === []) {
1 ✔
3667
                return false;
×
3668
            }
3669

3670
            foreach ($key as $keyTmp) {
1 ✔
3671
                $found = ($this->get($keyTmp, $UN_FOUND) !== $UN_FOUND);
1 ✔
3672
                if ($found === false) {
1 ✔
3673
                    return false;
1 ✔
3674
                }
3675
            }
3676

3677
            return true;
1 ✔
3678
        }
3679

3680
        return $this->get($key, $UN_FOUND) !== $UN_FOUND;
29 ✔
3681
    }
3682

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

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

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

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

3749
        foreach ($this->getGenerator() as $a) {
4 ✔
3750
            if (\array_key_exists($key, $a) === true) {
4 ✔
3751
                $results[$a[$key]] = $a;
3 ✔
3752
            }
3753
        }
3754

3755
        return static::create(
4 ✔
3756
            $results,
4 ✔
3757
            $this->iteratorClass,
4 ✔
3758
            false
4 ✔
3759
        );
4 ✔
3760
    }
3761

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

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

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

3837
        return static::create(
3 ✔
3838
            \array_values(\array_intersect($this->toArray(), $search)),
3 ✔
3839
            $this->iteratorClass,
3 ✔
3840
            false
3 ✔
3841
        );
3 ✔
3842
    }
3843

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

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

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

3907
        // If the callable has arguments, pass them.
3908
        if ($arguments) {
1 ✔
3909
            $array = \array_map($callable, $this->toArray(), $arguments);
1 ✔
3910
        } else {
3911
            $array = $this->map($callable);
1 ✔
3912
        }
3913

3914
        return static::create(
1 ✔
3915
            $array,
1 ✔
3916
            $this->iteratorClass,
1 ✔
3917
            false
1 ✔
3918
        );
1 ✔
3919
    }
3920

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

3939
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
3940
        foreach ($this->keys($recursive)->getGeneratorByReference() as &$key) {
13 ✔
3941
            if ((string) $key !== $key) {
13 ✔
3942
                return false;
11 ✔
3943
            }
3944
        }
3945

3946
        return true;
3 ✔
3947
    }
3948

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

3964
        if ($keys === null) {
45 ✔
3965
            return $this->array === [];
43 ✔
3966
        }
3967

3968
        foreach ((array) $keys as $key) {
2 ✔
3969
            if (!empty($this->get($key))) {
2 ✔
3970
                return false;
2 ✔
3971
            }
3972
        }
3973

3974
        return true;
2 ✔
3975
    }
3976

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

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

4012
        return false;
18 ✔
4013
    }
4014

4015
    /**
4016
     * Check whether array is numeric or not.
4017
     *
4018
     * @return bool
4019
     *              <p>Returns true if numeric, false otherwise.</p>
4020
     */
4021
    public function isNumeric(): bool
4022
    {
4023
        if ($this->isEmpty()) {
5 ✔
4024
            return false;
2 ✔
4025
        }
4026

4027
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4028
        foreach ($this->keys()->getGeneratorByReference() as &$key) {
4 ✔
4029
            if ((int) $key !== $key) {
4 ✔
4030
                return false;
2 ✔
4031
            }
4032
        }
4033

4034
        return true;
2 ✔
4035
    }
4036

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

4065
            if ($key !== $i) {
9 ✔
4066
                return false;
3 ✔
4067
            }
4068

4069
            ++$i;
8 ✔
4070
        }
4071

4072
        return !($i === 0);
9 ✔
4073
    }
4074

4075
    /**
4076
     * @return array
4077
     *
4078
     * @phpstan-return array<TKey,T>
4079
     */
4080
    public function jsonSerialize(): array
4081
    {
4082
        /** @var array<TKey,T> $return */
4083
        $return = $this->toArray();
2 ✔
4084

4085
        return $return;
2 ✔
4086
    }
4087

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

4100
        return \key($this->array);
×
4101
    }
4102

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

4122
        return false;
146 ✔
4123
    }
4124

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

4160
        if ($recursive === true) {
30 ✔
4161
            $array = $this->array_keys_recursive(
4 ✔
4162
                null,
4 ✔
4163
                $search_values,
4 ✔
4164
                $strict
4 ✔
4165
            );
4 ✔
4166

4167
            return static::create(
4 ✔
4168
                $array,
4 ✔
4169
                $this->iteratorClass,
4 ✔
4170
                false
4 ✔
4171
            );
4 ✔
4172
        }
4173

4174
        // non recursive
4175

4176
        if ($search_values === null) {
29 ✔
4177
            $arrayFunction = function (): \Generator {
29 ✔
4178
                foreach ($this->getGenerator() as $key => $value) {
29 ✔
4179
                    yield $key;
27 ✔
4180
                }
4181
            };
29 ✔
4182
        } else {
4183
            $arrayFunction = function () use ($search_values, $strict): \Generator {
1 ✔
4184
                $is_array_tmp = \is_array($search_values);
1 ✔
4185

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

4217
        return static::create(
29 ✔
4218
            $arrayFunction,
29 ✔
4219
            $this->iteratorClass,
29 ✔
4220
            false
29 ✔
4221
        );
29 ✔
4222
    }
4223

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

4242
        \krsort($this->array, $sort_flags);
4 ✔
4243

4244
        return $this;
4 ✔
4245
    }
4246

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

4266
        /**
4267
         * @psalm-suppress ImpureMethodCall - object is already cloned
4268
         */
4269
        $that->krsort($sort_flags);
4 ✔
4270

4271
        return $that;
4 ✔
4272
    }
4273

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

4294
        /** @var T $value_last */
4295
        $value_last = $this->get($key_last);
16 ✔
4296

4297
        return $value_last;
16 ✔
4298
    }
4299

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

4313
        /** @phpstan-var TKey|null $return - help for phpstan */
4314
        $return = \array_key_last($this->array);
22 ✔
4315

4316
        return $return;
22 ✔
4317
    }
4318

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

4344
        if ($number === null) {
12 ✔
4345
            $poppedValue = $this->last();
8 ✔
4346

4347
            if ($poppedValue === null) {
8 ✔
4348
                $poppedValue = [$poppedValue];
1 ✔
4349
            } else {
4350
                $poppedValue = (array) $poppedValue;
7 ✔
4351
            }
4352

4353
            $arrayy = static::create(
8 ✔
4354
                $poppedValue,
8 ✔
4355
                $this->iteratorClass,
8 ✔
4356
                false
8 ✔
4357
            );
8 ✔
4358
        } else {
4359
            $arrayy = $this->rest(-$number);
4 ✔
4360
        }
4361

4362
        return $arrayy;
12 ✔
4363
    }
4364

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

4385
        $this->array = $this->lastsImmutable($number)->toArray();
12 ✔
4386
        $this->generator = null;
12 ✔
4387

4388
        return $this;
12 ✔
4389
    }
4390

4391
    /**
4392
     * Count the values from the current array.
4393
     *
4394
     * alias: for "Arrayy->count()"
4395
     *
4396
     * @param int $mode
4397
     *
4398
     * @return int
4399
     *
4400
     * @see Arrayy::count()
4401
     */
4402
    public function length(int $mode = \COUNT_NORMAL): int
4403
    {
4404
        return $this->count($mode);
20 ✔
4405
    }
4406

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

4439
        return static::create(
7 ✔
4440
            function () use ($useArguments, $callable, $useKeyAsSecondParameter, $arguments) {
7 ✔
4441
                foreach ($this->getGenerator() as $key => $value) {
7 ✔
4442
                    if ($useArguments) {
6 ✔
4443
                        if ($useKeyAsSecondParameter) {
3 ✔
4444
                            yield $key => $callable($value, $key, ...$arguments);
×
4445
                        } else {
4446
                            yield $key => $callable($value, ...$arguments);
3 ✔
4447
                        }
4448
                    } else {
4449
                        /** @noinspection NestedPositiveIfStatementsInspection */
4450
                        if ($useKeyAsSecondParameter) {
6 ✔
4451
                            yield $key => $callable($value, $key);
×
4452
                        } else {
4453
                            yield $key => $callable($value);
6 ✔
4454
                        }
4455
                    }
4456
                }
4457
            },
7 ✔
4458
            $this->iteratorClass,
7 ✔
4459
            false
7 ✔
4460
        );
7 ✔
4461
    }
4462

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

4485
        foreach ($this->getGenerator() as $key => $value) {
13 ✔
4486
            $value = $closure($value, $key);
13 ✔
4487

4488
            if ($value === false) {
13 ✔
4489
                return false;
7 ✔
4490
            }
4491
        }
4492

4493
        return true;
7 ✔
4494
    }
4495

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

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

4521
            if ($value === true) {
12 ✔
4522
                return true;
9 ✔
4523
            }
4524
        }
4525

4526
        return false;
4 ✔
4527
    }
4528

4529
    /**
4530
     * Get the max value from an array.
4531
     *
4532
     * EXAMPLE: <code>
4533
     * a([-9, -8, -7, 1.32])->max(); // 1.32
4534
     * </code>
4535
     *
4536
     * @return false|float|int|string
4537
     *                                <p>Will return false if there are no values.</p>
4538
     */
4539
    public function max()
4540
    {
4541
        if ($this->count() === 0) {
11 ✔
4542
            return false;
1 ✔
4543
        }
4544

4545
        $max = false;
10 ✔
4546
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4547
        foreach ($this->getGeneratorByReference() as &$value) {
10 ✔
4548
            if (
4549
                $max === false
10 ✔
4550
                ||
4551
                $value > $max
10 ✔
4552
            ) {
4553
                $max = $value;
10 ✔
4554
            }
4555
        }
4556

4557
        return $max;
10 ✔
4558
    }
4559

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

4594
        return static::create(
33 ✔
4595
            $result,
33 ✔
4596
            $this->iteratorClass,
33 ✔
4597
            false
33 ✔
4598
        );
33 ✔
4599
    }
4600

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

4636
        return static::create(
20 ✔
4637
            $result,
20 ✔
4638
            $this->iteratorClass,
20 ✔
4639
            false
20 ✔
4640
        );
20 ✔
4641
    }
4642

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

4677
        return static::create(
17 ✔
4678
            $result,
17 ✔
4679
            $this->iteratorClass,
17 ✔
4680
            false
17 ✔
4681
        );
17 ✔
4682
    }
4683

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

4719
        return static::create(
21 ✔
4720
            $result,
21 ✔
4721
            $this->iteratorClass,
21 ✔
4722
            false
21 ✔
4723
        );
21 ✔
4724
    }
4725

4726
    /**
4727
     * Return a meta object with property names from phpdoc array-shape annotations,
4728
     * `@property` tags, and native declared properties.
4729
     *
4730
     * @return ArrayyMeta|mixed|static
4731
     */
4732
    public static function meta()
4733
    {
4734
        return (new ArrayyMeta())->getMetaObject(static::class);
46 ✔
4735
    }
4736

4737
    /**
4738
     * Get the min value from an array.
4739
     *
4740
     * EXAMPLE: <code>
4741
     * a([-9, -8, -7, 1.32])->min(); // -9
4742
     * </code>
4743
     *
4744
     * @return false|mixed
4745
     *                     <p>Will return false if there are no values.</p>
4746
     *
4747
     * @phpstan-return false|T
4748
     */
4749
    public function min()
4750
    {
4751
        if ($this->count() === 0) {
11 ✔
4752
            return false;
1 ✔
4753
        }
4754

4755
        $min = false;
10 ✔
4756
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
4757
        foreach ($this->getGeneratorByReference() as &$value) {
10 ✔
4758
            if (
4759
                $min === false
10 ✔
4760
                ||
4761
                $value < $min
10 ✔
4762
            ) {
4763
                $min = $value;
10 ✔
4764
            }
4765
        }
4766

4767
        return $min;
10 ✔
4768
    }
4769

4770
    /**
4771
     * Get the most used value from the array.
4772
     *
4773
     * @return mixed|null
4774
     *                    <p>(Immutable) Return null if there wasn't an element.</p>
4775
     *
4776
     * @phpstan-return T|null
4777
     * @psalm-mutation-free
4778
     */
4779
    public function mostUsedValue()
4780
    {
4781
        /* @phpstan-ignore return.type */
4782
        return $this->countValues()->arsortImmutable()->firstKey();
3 ✔
4783
    }
4784

4785
    /**
4786
     * Get the most used value from the array.
4787
     *
4788
     * @param int|null $number <p>How many values you will take?</p>
4789
     *
4790
     * @return static
4791
     *                <p>(Immutable)</p>
4792
     *
4793
     * @phpstan-return static
4794
     * @psalm-mutation-free
4795
     */
4796
    public function mostUsedValues(?int $number = null): self
4797
    {
4798
        return $this->countValues()->arsortImmutable()->firstsKeys($number);
3 ✔
4799
    }
4800

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

4822
        if ((int) $from === $from) {
1 ✔
4823
            $tmp = \array_splice($array, $from, 1);
1 ✔
4824
            \array_splice($array, (int) $to, 0, $tmp);
1 ✔
4825
            $output = $array;
1 ✔
4826
        } elseif ((string) $from === $from) {
1 ✔
4827
            $indexToMove = \array_search($from, \array_keys($array), true);
1 ✔
4828
            $itemToMove = $array[$from];
1 ✔
4829
            if ($indexToMove !== false) {
1 ✔
4830
                \array_splice($array, $indexToMove, 1);
1 ✔
4831
            }
4832
            $i = 0;
1 ✔
4833
            $output = [];
1 ✔
4834
            foreach ($array as $key => $item) {
1 ✔
4835
                if ($i === $to) {
1 ✔
4836
                    $output[$from] = $itemToMove;
1 ✔
4837
                }
4838
                $output[$key] = $item;
1 ✔
4839
                ++$i;
1 ✔
4840
            }
4841
        } else {
4842
            $output = [];
×
4843
        }
4844

4845
        return static::create(
1 ✔
4846
            $output,
1 ✔
4847
            $this->iteratorClass,
1 ✔
4848
            false
1 ✔
4849
        );
1 ✔
4850
    }
4851

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

4870
        if ($this->offsetExists($key)) {
1 ✔
4871
            $tmpValue = $this->get($key);
1 ✔
4872
            unset($array[$key]);
1 ✔
4873
            $array = [$key => $tmpValue] + $array;
1 ✔
4874
        }
4875

4876
        return static::create(
1 ✔
4877
            $array,
1 ✔
4878
            $this->iteratorClass,
1 ✔
4879
            false
1 ✔
4880
        );
1 ✔
4881
    }
4882

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

4901
        if ($this->offsetExists($key)) {
1 ✔
4902
            $tmpValue = $this->get($key);
1 ✔
4903
            unset($array[$key]);
1 ✔
4904
            $array += [$key => $tmpValue];
1 ✔
4905
        }
4906

4907
        return static::create(
1 ✔
4908
            $array,
1 ✔
4909
            $this->iteratorClass,
1 ✔
4910
            false
1 ✔
4911
        );
1 ✔
4912
    }
4913

4914
    /**
4915
     * Moves the internal iterator position to the next element and returns this element.
4916
     *
4917
     * @return false|mixed
4918
     *                     <p>(Mutable) Will return false if there are no values.</p>
4919
     *
4920
     * @phpstan-return false|T
4921
     */
4922
    public function next()
4923
    {
4924
        if ($this->generator) {
×
4925
            $this->generator->next();
×
4926

4927
            return $this->generator->current() ?? false;
×
4928
        }
4929

4930
        return \next($this->array);
×
4931
    }
4932

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

4954
                yield $key => $value;
1 ✔
4955
            }
4956
        };
1 ✔
4957

4958
        return static::create(
1 ✔
4959
            $arrayFunction,
1 ✔
4960
            $this->iteratorClass,
1 ✔
4961
            false
1 ✔
4962
        );
1 ✔
4963
    }
4964

4965
    /**
4966
     * Get a subset of the items from the given array.
4967
     *
4968
     * @param int[]|string[] $keys
4969
     *
4970
     * @return static
4971
     *                <p>(Immutable)</p>
4972
     *
4973
     * @phpstan-param array-key[] $keys
4974
     * @phpstan-return static
4975
     * @psalm-mutation-free
4976
     */
4977
    public function only(array $keys): self
4978
    {
4979
        $keys = \array_flip($keys);
1 ✔
4980

4981
        $generator = function () use ($keys): \Generator {
1 ✔
4982
            foreach ($this->getGenerator() as $key => $value) {
1 ✔
4983
                if (isset($keys[$key])) {
1 ✔
4984
                    yield $key => $value;
1 ✔
4985
                }
4986
            }
4987
        };
1 ✔
4988

4989
        return static::create(
1 ✔
4990
            $generator,
1 ✔
4991
            $this->iteratorClass,
1 ✔
4992
            false
1 ✔
4993
        );
1 ✔
4994
    }
4995

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

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

5038
        foreach ($this->getGenerator() as $key => $value) {
1 ✔
5039
            if ($closure($value, $key)) {
1 ✔
5040
                $matches[$key] = $value;
1 ✔
5041
            } else {
5042
                $noMatches[$key] = $value;
1 ✔
5043
            }
5044
        }
5045

5046
        return [self::create($matches), self::create($noMatches)];
1 ✔
5047
    }
5048

5049
    /**
5050
     * Pop a specified value off the end of the current array.
5051
     *
5052
     * @return T|null
5053
     *                    <p>(Mutable) The popped element from the current array or null if the array is e.g. empty.</p>
5054
     *
5055
     * @phpstan-return T|null
5056
     */
5057
    public function pop()
5058
    {
5059
        $this->generatorToArray();
5 ✔
5060

5061
        return \array_pop($this->array);
5 ✔
5062
    }
5063

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

5085
        if ($this->properties !== []) {
12 ✔
5086
            $this->checkType($key, $value);
4 ✔
5087
        }
5088

5089
        if ($key === null) {
10 ✔
5090
            \array_unshift($this->array, $value);
8 ✔
5091
        } else {
5092
            $this->array = [$key => $value] + $this->array;
3 ✔
5093
        }
5094

5095
        return $this;
10 ✔
5096
    }
5097

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

5123
            if ($key !== null) {
1 ✔
5124
                yield $key => $value;
×
5125
            } else {
5126
                yield $value;
1 ✔
5127
            }
5128

5129
            foreach ($this->getGenerator() as $keyOld => $itemOld) {
1 ✔
5130
                yield $keyOld => $itemOld;
1 ✔
5131
            }
5132
        };
1 ✔
5133

5134
        return static::create(
1 ✔
5135
            $generator,
1 ✔
5136
            $this->iteratorClass,
1 ✔
5137
            false
1 ✔
5138
        );
1 ✔
5139
    }
5140

5141
    /**
5142
     * Add a suffix to each key.
5143
     *
5144
     * @param float|int|string $suffix
5145
     *
5146
     * @return static
5147
     *                <p>(Immutable) Return an Arrayy object, with the prepended keys.</p>
5148
     *
5149
     * @phpstan-return static
5150
     * @psalm-mutation-free
5151
     */
5152
    public function prependToEachKey($suffix): self
5153
    {
5154
        // init
5155
        $result = [];
10 ✔
5156

5157
        foreach ($this->getGenerator() as $key => $item) {
10 ✔
5158
            if ($item instanceof self) {
9 ✔
5159
                $result[$key] = $item->prependToEachKey($suffix);
×
5160
            } elseif (\is_array($item)) {
9 ✔
5161
                $result[$key] = self::create(
×
5162
                    $item,
×
5163
                    $this->iteratorClass,
×
5164
                    false
×
5165
                )->prependToEachKey($suffix)
×
5166
                    ->toArray();
×
5167
            } else {
5168
                $result[$key . $suffix] = $item;
9 ✔
5169
            }
5170
        }
5171

5172
        return self::create(
10 ✔
5173
            $result,
10 ✔
5174
            $this->iteratorClass,
10 ✔
5175
            false
10 ✔
5176
        );
10 ✔
5177
    }
5178

5179
    /**
5180
     * Add a suffix to each value.
5181
     *
5182
     * @param float|int|string $suffix
5183
     *
5184
     * @return static
5185
     *                <p>(Immutable) Return an Arrayy object, with the prepended values.</p>
5186
     *
5187
     * @phpstan-return static
5188
     * @psalm-mutation-free
5189
     */
5190
    public function prependToEachValue($suffix): self
5191
    {
5192
        // init
5193
        $result = [];
10 ✔
5194

5195
        foreach ($this->getGenerator() as $key => $item) {
10 ✔
5196
            if ($item instanceof self) {
9 ✔
5197
                $result[$key] = $item->prependToEachValue($suffix);
×
5198
            } elseif (\is_array($item)) {
9 ✔
5199
                $result[$key] = self::create(
×
5200
                    $item,
×
5201
                    $this->iteratorClass,
×
5202
                    false
×
5203
                )->prependToEachValue($suffix)
×
5204
                    ->toArray();
×
5205
            } elseif (\is_object($item) === true) {
9 ✔
5206
                $result[$key] = $item;
1 ✔
5207
            } else {
5208
                $result[$key] = $item . $suffix;
8 ✔
5209
            }
5210
        }
5211

5212
        return self::create(
10 ✔
5213
            $result,
10 ✔
5214
            $this->iteratorClass,
10 ✔
5215
            false
10 ✔
5216
        );
10 ✔
5217
    }
5218

5219
    /**
5220
     * Return the value of a given key and
5221
     * delete the key.
5222
     *
5223
     * @param int|int[]|string|string[]|null $keyOrKeys
5224
     * @param mixed                          $fallback
5225
     *
5226
     * @return mixed
5227
     *
5228
     * @template TFallback $fallback
5229
     * @phpstan-param TFallback $fallback
5230
     * @phpstan-return TFallback|T|T[]
5231
     */
5232
    public function pull($keyOrKeys = null, $fallback = null)
5233
    {
5234
        if ($keyOrKeys === null) {
6 ✔
5235
            $array = $this->toArray();
1 ✔
5236
            $this->clear();
1 ✔
5237

5238
            return $array;
1 ✔
5239
        }
5240

5241
        if (\is_array($keyOrKeys)) {
5 ✔
5242
            $valueOrValues = [];
1 ✔
5243
            foreach ($keyOrKeys as $key) {
1 ✔
5244
                $valueOrValues[] = $this->get($key, $fallback);
1 ✔
5245
                $this->offsetUnset($key);
1 ✔
5246
            }
5247
        } else {
5248
            $valueOrValues = $this->get($keyOrKeys, $fallback);
5 ✔
5249
            $this->offsetUnset($keyOrKeys);
5 ✔
5250
        }
5251

5252
        /** @var T|T[]|TFallback $valueOrValues */
5253
        return $valueOrValues;
5 ✔
5254
    }
5255

5256
    /**
5257
     * Push one or more values onto the end of array at once.
5258
     *
5259
     * @param mixed ...$args
5260
     *
5261
     * @return $this
5262
     *               <p>(Mutable) Return this Arrayy object, with pushed elements to the end of array.</p>
5263
     *
5264
     * @noinspection ReturnTypeCanBeDeclaredInspection
5265
     *
5266
     * @phpstan-param  array<TKey,T> ...$args
5267
     * @phpstan-return static
5268
     */
5269
    public function push(...$args)
5270
    {
5271
        $this->generatorToArray();
9 ✔
5272

5273
        if (
5274
            $this->checkPropertyTypes
9 ✔
5275
            &&
5276
            $this->properties !== []
9 ✔
5277
        ) {
5278
            foreach ($args as $key => $value) {
3 ✔
5279
                $this->checkType($key, $value);
3 ✔
5280
            }
5281
        }
5282

5283
        \array_push($this->array, ...$args); // @phpstan-ignore assign.propertyType
8 ✔
5284

5285
        return $this;
8 ✔
5286
    }
5287

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

5306
        if ($this->count() === 0) {
20 ✔
5307
            return static::create(
1 ✔
5308
                [],
1 ✔
5309
                $this->iteratorClass,
1 ✔
5310
                false
1 ✔
5311
            );
1 ✔
5312
        }
5313

5314
        if ($number === null) {
19 ✔
5315
            $arrayRandValue = [$this->array[\array_rand($this->array)]];
14 ✔
5316

5317
            return static::create(
14 ✔
5318
                $arrayRandValue,
14 ✔
5319
                $this->iteratorClass,
14 ✔
5320
                false
14 ✔
5321
            );
14 ✔
5322
        }
5323

5324
        $arrayTmp = $this->array;
6 ✔
5325
        \shuffle($arrayTmp);
6 ✔
5326

5327
        return static::create(
6 ✔
5328
            $arrayTmp,
6 ✔
5329
            $this->iteratorClass,
6 ✔
5330
            false
6 ✔
5331
        )->firstsImmutable($number);
6 ✔
5332
    }
5333

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

5353
        if (!isset($result[0])) {
4 ✔
5354
            $result[0] = null;
×
5355
        }
5356

5357
        return $result[0];
4 ✔
5358
    }
5359

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

5380
        $count = $this->count();
13 ✔
5381

5382
        if (
5383
            $number === 0
13 ✔
5384
            ||
5385
            $number > $count
13 ✔
5386
        ) {
5387
            throw new \RangeException(
2 ✔
5388
                \sprintf(
2 ✔
5389
                    'Number of requested keys (%s) must be equal or lower than number of elements in this array (%s)',
2 ✔
5390
                    $number,
2 ✔
5391
                    $count
2 ✔
5392
                )
2 ✔
5393
            );
2 ✔
5394
        }
5395

5396
        $result = (array) \array_rand($this->array, $number);
11 ✔
5397

5398
        return static::create(
11 ✔
5399
            $result,
11 ✔
5400
            $this->iteratorClass,
11 ✔
5401
            false
11 ✔
5402
        );
11 ✔
5403
    }
5404

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

5423
        if ($this->count() === 0) {
17 ✔
5424
            return static::create(
×
5425
                [],
×
5426
                $this->iteratorClass,
×
5427
                false
×
5428
            );
×
5429
        }
5430

5431
        if ($number === null) {
17 ✔
5432
            $arrayRandValue = [$this->array[\array_rand($this->array)]];
7 ✔
5433
            $this->array = $arrayRandValue;
7 ✔
5434

5435
            return $this;
7 ✔
5436
        }
5437

5438
        \shuffle($this->array);
11 ✔
5439

5440
        return $this->firstsMutable($number);
11 ✔
5441
    }
5442

5443
    /**
5444
     * Pick a random value from the values of this array.
5445
     *
5446
     * EXAMPLE: <code>
5447
     * a([1 => 'one', 2 => 'two'])->randomValue(); // e.g. 'one'
5448
     * </code>
5449
     *
5450
     * @return T|static|null
5451
     *               <p>Get a random value or null if there wasn't a value.</p>
5452
     *
5453
     * @phpstan-return (T is array ? static : T)|null
5454
     */
5455
    public function randomValue()
5456
    {
5457
        $result = $this->randomImmutable();
5 ✔
5458

5459
        if (!isset($result[0])) {
5 ✔
5460
            $result[0] = null;
×
5461
        }
5462

5463
        return $result[0];
5 ✔
5464
    }
5465

5466
    /**
5467
     * Pick a given number of random values out of this array.
5468
     *
5469
     * EXAMPLE: <code>
5470
     * a([1 => 'one', 2 => 'two'])->randomValues(); // e.g. Arrayy['one', 'two']
5471
     * </code>
5472
     *
5473
     * @param int $number
5474
     *
5475
     * @return static
5476
     *                <p>(Mutable)</p>
5477
     *
5478
     * @phpstan-return static
5479
     */
5480
    public function randomValues(int $number): self
5481
    {
5482
        return $this->randomMutable($number);
7 ✔
5483
    }
5484

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

5506
        foreach ($array as $option => $weight) {
9 ✔
5507
            if ($this->searchIndex($option) !== false) {
9 ✔
5508
                for ($i = 0; $i < $weight; ++$i) {
2 ✔
5509
                    $options[] = $option;
1 ✔
5510
                }
5511
            }
5512
        }
5513

5514
        return $this->mergeAppendKeepIndex($options)->randomImmutable($number);
9 ✔
5515
    }
5516

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

5550
        /** @var static $return - help for phpstan */
5551
        $return = static::create(
18 ✔
5552
            $initial,
18 ✔
5553
            $this->iteratorClass,
18 ✔
5554
            false
18 ✔
5555
        );
18 ✔
5556

5557
        return $return;
18 ✔
5558
    }
5559

5560
    /**
5561
     * @param bool $unique
5562
     *
5563
     * @return static
5564
     *                <p>(Immutable)</p>
5565
     *
5566
     * @phpstan-return static
5567
     * @psalm-mutation-free
5568
     */
5569
    public function reduce_dimension(bool $unique = true): self
5570
    {
5571
        // init
5572
        $result = [];
14 ✔
5573

5574
        foreach ($this->getGenerator() as $val) {
14 ✔
5575
            if (\is_array($val)) {
12 ✔
5576
                $result[] = static::create($val)->reduce_dimension($unique)->toArray();
5 ✔
5577
            } else {
5578
                $result[] = [$val];
12 ✔
5579
            }
5580
        }
5581

5582
        $result = $result === [] ? [] : \array_merge(...$result);
14 ✔
5583

5584
        $resultArrayy = static::create($result);
14 ✔
5585

5586
        /**
5587
         * @psalm-suppress ImpureMethodCall - object is already re-created
5588
         * @psalm-suppress InvalidReturnStatement - why?
5589
         */
5590
        return $unique ? $resultArrayy->unique() : $resultArrayy;
14 ✔
5591
    }
5592

5593
    /**
5594
     * Create a numerically re-indexed Arrayy object.
5595
     *
5596
     * EXAMPLE: <code>
5597
     * a([2 => 1, 3 => 2])->reindex(); // Arrayy[0 => 1, 1 => 2]
5598
     * </code>
5599
     *
5600
     * @return $this
5601
     *               <p>(Mutable) Return this Arrayy object, with re-indexed array-elements.</p>
5602
     *
5603
     * @phpstan-return static
5604
     */
5605
    public function reindex(): self
5606
    {
5607
        $this->generatorToArray(false);
9 ✔
5608

5609
        $this->array = \array_values($this->array);
9 ✔
5610

5611
        return $this;
9 ✔
5612
    }
5613

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

5638
        foreach ($this->getGenerator() as $key => $value) {
1 ✔
5639
            if (!$closure($value, $key)) {
1 ✔
5640
                $filtered[$key] = $value;
1 ✔
5641
            }
5642
        }
5643

5644
        return static::create(
1 ✔
5645
            $filtered,
1 ✔
5646
            $this->iteratorClass,
1 ✔
5647
            false
1 ✔
5648
        );
1 ✔
5649
    }
5650

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

5674
            return static::create(
1 ✔
5675
                $this->toArray(),
1 ✔
5676
                $this->iteratorClass,
1 ✔
5677
                false
1 ✔
5678
            );
1 ✔
5679
        }
5680

5681
        $this->internalRemove($key);
28 ✔
5682

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

5690
    /**
5691
     * alias: for "Arrayy->removeValue()"
5692
     *
5693
     * @param mixed $element
5694
     *
5695
     * @return static
5696
     *                <p>(Immutable)</p>
5697
     *
5698
     * @phpstan-param  T $element
5699
     * @phpstan-return static
5700
     * @psalm-mutation-free
5701
     */
5702
    public function removeElement($element)
5703
    {
5704
        return $this->removeValue($element);
8 ✔
5705
    }
5706

5707
    /**
5708
     * Remove the first value from the current array.
5709
     *
5710
     * EXAMPLE: <code>
5711
     * a([1 => 'bar', 'foo' => 'foo'])->removeFirst(); // Arrayy['foo' => 'foo']
5712
     * </code>
5713
     *
5714
     * @return static
5715
     *                <p>(Immutable)</p>
5716
     *
5717
     * @phpstan-return static
5718
     * @psalm-mutation-free
5719
     */
5720
    public function removeFirst(): self
5721
    {
5722
        $tmpArray = $this->toArray();
7 ✔
5723

5724
        \array_shift($tmpArray);
7 ✔
5725

5726
        return static::create(
7 ✔
5727
            $tmpArray,
7 ✔
5728
            $this->iteratorClass,
7 ✔
5729
            false
7 ✔
5730
        );
7 ✔
5731
    }
5732

5733
    /**
5734
     * Remove the last value from the current array.
5735
     *
5736
     * EXAMPLE: <code>
5737
     * a([1 => 'bar', 'foo' => 'foo'])->removeLast(); // Arrayy[1 => 'bar']
5738
     * </code>
5739
     *
5740
     * @return static
5741
     *                <p>(Immutable)</p>
5742
     *
5743
     * @phpstan-return static
5744
     * @psalm-mutation-free
5745
     */
5746
    public function removeLast(): self
5747
    {
5748
        $tmpArray = $this->toArray();
7 ✔
5749

5750
        \array_pop($tmpArray);
7 ✔
5751

5752
        return static::create(
7 ✔
5753
            $tmpArray,
7 ✔
5754
            $this->iteratorClass,
7 ✔
5755
            false
7 ✔
5756
        );
7 ✔
5757
    }
5758

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

5779
        // init
5780
        $isSequentialArray = $this->isSequential();
8 ✔
5781

5782
        foreach ($this->array as $key => $item) {
8 ✔
5783
            if ($item === $value) {
7 ✔
5784
                unset($this->array[$key]);
7 ✔
5785
            }
5786
        }
5787

5788
        if ($isSequentialArray) {
8 ✔
5789
            $this->array = \array_values($this->array);
6 ✔
5790
        }
5791

5792
        return static::create(
8 ✔
5793
            $this->array,
8 ✔
5794
            $this->iteratorClass,
8 ✔
5795
            false
8 ✔
5796
        );
8 ✔
5797
    }
5798

5799
    /**
5800
     * Generate array of repeated arrays.
5801
     *
5802
     * @param int $times <p>How many times has to be repeated.</p>
5803
     *
5804
     * @return static
5805
     *                <p>(Immutable)</p>
5806
     *
5807
     * @phpstan-return static
5808
     * @psalm-mutation-free
5809
     */
5810
    public function repeat($times): self
5811
    {
5812
        if ($times === 0) {
1 ✔
5813
            return static::create([], $this->iteratorClass);
1 ✔
5814
        }
5815

5816
        return static::create(
1 ✔
5817
            \array_fill(0, (int) $times, $this->toArray()),
1 ✔
5818
            $this->iteratorClass,
1 ✔
5819
            false
1 ✔
5820
        );
1 ✔
5821
    }
5822

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

5848
        /**
5849
         * @psalm-suppress ImpureMethodCall - object is already cloned
5850
         */
5851
        return $that->remove($oldKey)
5 ✔
5852
            ->set($newKey, $newValue);
5 ✔
5853
    }
5854

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

5892
        return static::create(
2 ✔
5893
            $data,
2 ✔
5894
            $this->iteratorClass,
2 ✔
5895
            false
2 ✔
5896
        );
2 ✔
5897
    }
5898

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

5936
        return static::create(
2 ✔
5937
            $data,
2 ✔
5938
            $this->iteratorClass,
2 ✔
5939
            false
2 ✔
5940
        );
2 ✔
5941
    }
5942

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

5968
        return static::create(
1 ✔
5969
            $result,
1 ✔
5970
            $this->iteratorClass,
1 ✔
5971
            false
1 ✔
5972
        );
1 ✔
5973
    }
5974

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

5999
        if ($key !== false) {
3 ✔
6000
            $array[$key] = $replacement;
3 ✔
6001
        }
6002

6003
        return static::create(
3 ✔
6004
            $array,
3 ✔
6005
            $this->iteratorClass,
3 ✔
6006
            false
3 ✔
6007
        );
3 ✔
6008
    }
6009

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

6033
        /* @phpstan-ignore argument.type */
6034
        return $this->each($callable);
1 ✔
6035
    }
6036

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

6056
        return static::create(
15 ✔
6057
            \array_splice($tmpArray, $from),
15 ✔
6058
            $this->iteratorClass,
15 ✔
6059
            false
15 ✔
6060
        );
15 ✔
6061
    }
6062

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

6079
        $this->array = \array_reverse($this->array);
9 ✔
6080

6081
        return $this;
9 ✔
6082
    }
6083

6084
    /**
6085
     * Return the array with keys in the reverse order.
6086
     *
6087
     * EXAMPLE: <code>
6088
     * a([1 => 1, 2 => 2, 3 => 3])->reverse(); // self[3 => 3, 2 => 2, 1 => 1]
6089
     * </code>
6090
     *
6091
     * @return $this
6092
     *               <p>(Mutable) Return this Arrayy object.</p>
6093
     *
6094
     * @phpstan-return static
6095
     */
6096
    public function reverseKeepIndex(): self
6097
    {
6098
        $this->generatorToArray();
5 ✔
6099

6100
        $this->array = \array_reverse($this->array, true);
5 ✔
6101

6102
        return $this;
5 ✔
6103
    }
6104

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

6123
        \rsort($this->array, $sort_flags);
4 ✔
6124

6125
        return $this;
4 ✔
6126
    }
6127

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

6147
        /**
6148
         * @psalm-suppress ImpureMethodCall - object is already cloned
6149
         */
6150
        $that->rsort($sort_flags);
4 ✔
6151

6152
        return $that;
4 ✔
6153
    }
6154

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

6180
        return false;
11 ✔
6181
    }
6182

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

6203
        // init
6204
        $return = [];
9 ✔
6205

6206
        if ($this->array === []) {
9 ✔
6207
            return static::create(
×
6208
                [],
×
6209
                $this->iteratorClass,
×
6210
                false
×
6211
            );
×
6212
        }
6213

6214
        // php cast "bool"-index into "int"-index
6215
        /* @phpstan-ignore identical.alwaysFalse */
6216
        if ((bool) $index === $index) {
9 ✔
6217
            $index = (int) $index;
1 ✔
6218
        }
6219

6220
        if ($this->offsetExists($index)) {
9 ✔
6221
            $return = [$this->array[$index]];
7 ✔
6222
        }
6223

6224
        return static::create(
9 ✔
6225
            $return,
9 ✔
6226
            $this->iteratorClass,
9 ✔
6227
            false
9 ✔
6228
        );
9 ✔
6229
    }
6230

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

6253
        return $this;
28 ✔
6254
    }
6255

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

6280
        // If the key doesn't exist, set it.
6281
        if (!$this->has($key)) {
11 ✔
6282
            $this->array = $this->set($key, $fallback)->toArray();
4 ✔
6283
        }
6284

6285
        return $this->get($key);
11 ✔
6286
    }
6287

6288
    /**
6289
     * Shifts a specified value off the beginning of array.
6290
     *
6291
     * @return T|null
6292
     *                    <p>(Mutable) A shifted element from the current array.</p>
6293
     *
6294
     * @phpstan-return T|null
6295
     */
6296
    public function shift()
6297
    {
6298
        $this->generatorToArray();
5 ✔
6299

6300
        return \array_shift($this->array);
5 ✔
6301
    }
6302

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

6325
        if ($secure !== true) {
2 ✔
6326
            \shuffle($array);
2 ✔
6327
        } else {
6328
            $size = \count($array, \COUNT_NORMAL);
1 ✔
6329
            $keys = \array_keys($array);
1 ✔
6330
            for ($i = $size - 1; $i > 0; --$i) {
1 ✔
6331
                try {
6332
                    $r = \random_int(0, $i);
1 ✔
6333
                } catch (\Exception $e) {
×
6334
                    /** @noinspection RandomApiMigrationInspection - "random_int" is already in use */
6335
                    $r = \mt_rand(0, $i);
×
6336
                }
6337
                if ($r !== $i) {
1 ✔
6338
                    $temp = $array[$keys[$r]];
×
6339
                    $array[$keys[$r]] = $array[$keys[$i]];
×
6340
                    $array[$keys[$i]] = $temp;
×
6341
                }
6342
            }
6343
        }
6344

6345
        foreach ($array as $key => $value) {
2 ✔
6346
            // check if recursive is needed
6347
            if (\is_array($value)) {
2 ✔
6348
                /** @noinspection PhpSillyAssignmentInspection - hack for phpstan */
6349
                /** @phpstan-var array<TKey,T> $value */
6350
                $value = $value;
×
6351

6352
                $array[$key] = $this->shuffle($secure, $value);
×
6353
            }
6354
        }
6355

6356
        return static::create(
2 ✔
6357
            $array,
2 ✔
6358
            $this->iteratorClass,
2 ✔
6359
            false
2 ✔
6360
        );
2 ✔
6361
    }
6362

6363
    /**
6364
     * Count the values from the current array.
6365
     *
6366
     * alias: for "Arrayy->count()"
6367
     *
6368
     * @param int $mode
6369
     *
6370
     * @return int
6371
     */
6372
    public function size(int $mode = \COUNT_NORMAL): int
6373
    {
6374
        return $this->count($mode);
20 ✔
6375
    }
6376

6377
    /**
6378
     * Checks whether array has exactly $size items.
6379
     *
6380
     * @param int $size
6381
     *
6382
     * @return bool
6383
     */
6384
    public function sizeIs(int $size): bool
6385
    {
6386
        // init
6387
        $itemsTempCount = 0;
1 ✔
6388

6389
        /** @noinspection PhpUnusedLocalVariableInspection */
6390
        /** @noinspection PhpParameterByRefIsNotUsedAsReferenceInspection */
6391
        foreach ($this->getGeneratorByReference() as &$value) {
1 ✔
6392
            ++$itemsTempCount;
1 ✔
6393
            if ($itemsTempCount > $size) {
1 ✔
6394
                return false;
1 ✔
6395
            }
6396
        }
6397

6398
        return $itemsTempCount === $size;
1 ✔
6399
    }
6400

6401
    /**
6402
     * Checks whether array has between $fromSize to $toSize items. $toSize can be
6403
     * smaller than $fromSize.
6404
     *
6405
     * @param int $fromSize
6406
     * @param int $toSize
6407
     *
6408
     * @return bool
6409
     */
6410
    public function sizeIsBetween(int $fromSize, int $toSize): bool
6411
    {
6412
        if ($fromSize > $toSize) {
1 ✔
6413
            $tmp = $toSize;
1 ✔
6414
            $toSize = $fromSize;
1 ✔
6415
            $fromSize = $tmp;
1 ✔
6416
        }
6417

6418
        // init
6419
        $itemsTempCount = 0;
1 ✔
6420

6421
        /** @noinspection PhpUnusedLocalVariableInspection */
6422
        foreach ($this->getGenerator() as $value) {
1 ✔
6423
            ++$itemsTempCount;
1 ✔
6424
            if ($itemsTempCount > $toSize) {
1 ✔
6425
                return false;
1 ✔
6426
            }
6427
        }
6428

6429
        return $fromSize < $itemsTempCount && $itemsTempCount < $toSize;
1 ✔
6430
    }
6431

6432
    /**
6433
     * Checks whether array has more than $size items.
6434
     *
6435
     * @param int $size
6436
     *
6437
     * @return bool
6438
     */
6439
    public function sizeIsGreaterThan(int $size): bool
6440
    {
6441
        // init
6442
        $itemsTempCount = 0;
1 ✔
6443

6444
        /** @noinspection PhpUnusedLocalVariableInspection */
6445
        foreach ($this->getGenerator() as $value) {
1 ✔
6446
            ++$itemsTempCount;
1 ✔
6447
            if ($itemsTempCount > $size) {
1 ✔
6448
                return true;
1 ✔
6449
            }
6450
        }
6451

6452
        return $itemsTempCount > $size;
1 ✔
6453
    }
6454

6455
    /**
6456
     * Checks whether array has less than $size items.
6457
     *
6458
     * @param int $size
6459
     *
6460
     * @return bool
6461
     */
6462
    public function sizeIsLessThan(int $size): bool
6463
    {
6464
        // init
6465
        $itemsTempCount = 0;
1 ✔
6466

6467
        /** @noinspection PhpUnusedLocalVariableInspection */
6468
        foreach ($this->getGenerator() as $value) {
1 ✔
6469
            ++$itemsTempCount;
1 ✔
6470
            if ($itemsTempCount > $size) {
1 ✔
6471
                return false;
1 ✔
6472
            }
6473
        }
6474

6475
        return $itemsTempCount < $size;
1 ✔
6476
    }
6477

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

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

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

6565
        return $this->sorting(
20 ✔
6566
            $this->array,
20 ✔
6567
            $direction,
20 ✔
6568
            $strategy,
20 ✔
6569
            $keepKeys
20 ✔
6570
        );
20 ✔
6571
    }
6572

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

6593
        $that->generatorToArray();
12 ✔
6594

6595
        return $that->sorting(
12 ✔
6596
            $that->array,
12 ✔
6597
            $direction,
12 ✔
6598
            $strategy,
12 ✔
6599
            $keepKeys
12 ✔
6600
        );
12 ✔
6601
    }
6602

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

6628
        $this->sorterKeys($this->array, $direction, $strategy);
18 ✔
6629

6630
        return $this;
18 ✔
6631
    }
6632

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

6655
        /**
6656
         * @psalm-suppress ImpureMethodCall - object is already cloned
6657
         */
6658
        $that->sortKeys($direction, $strategy);
8 ✔
6659

6660
        return $that;
8 ✔
6661
    }
6662

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

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

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

6741
        // Transform all values into their results.
6742
        if ($sorter) {
1 ✔
6743
            $arrayy = static::create(
1 ✔
6744
                $array,
1 ✔
6745
                $this->iteratorClass,
1 ✔
6746
                false
1 ✔
6747
            );
1 ✔
6748

6749
            /**
6750
             * @psalm-suppress MissingClosureReturnType
6751
             * @psalm-suppress MissingClosureParamType
6752
             */
6753
            $results = $arrayy->each(
1 ✔
6754
                static function ($value) use ($sorter) {
1 ✔
6755
                    if (\is_callable($sorter) === true) {
1 ✔
6756
                        return $sorter($value);
1 ✔
6757
                    }
6758

6759
                    return $sorter === $value;
1 ✔
6760
                }
1 ✔
6761
            );
1 ✔
6762

6763
            $results = $results->toArray();
1 ✔
6764
        } else {
6765
            $results = $array;
1 ✔
6766
        }
6767

6768
        // Sort by the results and replace by original values
6769
        \array_multisort($results, $direction, $strategy, $array);
1 ✔
6770

6771
        return static::create(
1 ✔
6772
            $array,
1 ✔
6773
            $this->iteratorClass,
1 ✔
6774
            false
1 ✔
6775
        );
1 ✔
6776
    }
6777

6778
    /**
6779
     * @param int      $offset
6780
     * @param int|null $length
6781
     * @param array    $replacement
6782
     *
6783
     * @return static
6784
     *                <p>(Immutable)</p>
6785
     *
6786
     * @phpstan-param  array<T> $replacement
6787
     * @phpstan-return static
6788
     * @psalm-mutation-free
6789
     */
6790
    public function splice(int $offset, ?int $length = null, $replacement = []): self
6791
    {
6792
        $tmpArray = $this->toArray();
1 ✔
6793

6794
        \array_splice(
1 ✔
6795
            $tmpArray,
1 ✔
6796
            $offset,
1 ✔
6797
            $length ?? $this->count(),
1 ✔
6798
            $replacement
1 ✔
6799
        );
1 ✔
6800

6801
        return static::create(
1 ✔
6802
            $tmpArray,
1 ✔
6803
            $this->iteratorClass,
1 ✔
6804
            false
1 ✔
6805
        );
1 ✔
6806
    }
6807

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

6833
                    if ($i % $numberOfPieces !== 0) {
1 ✔
6834
                        ++$i;
1 ✔
6835

6836
                        continue;
1 ✔
6837
                    }
6838

6839
                    yield $carry;
1 ✔
6840

6841
                    $carry = [];
1 ✔
6842
                    $i = 1;
1 ✔
6843
                }
6844

6845
                if ($carry !== []) {
1 ✔
6846
                    yield $carry;
1 ✔
6847
                }
6848
            };
1 ✔
6849
        } else {
6850
            $generator = function () use ($numberOfPieces) {
1 ✔
6851
                $carry = [];
1 ✔
6852
                $i = 1;
1 ✔
6853
                foreach ($this->getGenerator() as $value) {
1 ✔
6854
                    $carry[] = $value;
1 ✔
6855

6856
                    if ($i % $numberOfPieces !== 0) {
1 ✔
6857
                        ++$i;
1 ✔
6858

6859
                        continue;
1 ✔
6860
                    }
6861

6862
                    yield $carry;
1 ✔
6863

6864
                    $carry = [];
1 ✔
6865
                    $i = 1;
1 ✔
6866
                }
6867

6868
                if ($carry !== []) {
1 ✔
6869
                    yield $carry;
1 ✔
6870
                }
6871
            };
1 ✔
6872
        }
6873

6874
        return static::create(
1 ✔
6875
            $generator,
1 ✔
6876
            $this->iteratorClass,
1 ✔
6877
            false
1 ✔
6878
        );
1 ✔
6879
    }
6880

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

6902
                if ((bool) \trim((string) $item)) {
1 ✔
6903
                    yield $key => $item;
1 ✔
6904
                }
6905
            }
6906
        };
1 ✔
6907

6908
        return static::create(
1 ✔
6909
            $generator(),
1 ✔
6910
            $this->iteratorClass,
1 ✔
6911
            false
1 ✔
6912
        );
1 ✔
6913
    }
6914

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

6935
        list($array[$swapA], $array[$swapB]) = [$array[$swapB], $array[$swapA]];
1 ✔
6936

6937
        return static::create(
1 ✔
6938
            $array,
1 ✔
6939
            $this->iteratorClass,
1 ✔
6940
            false
1 ✔
6941
        );
1 ✔
6942
    }
6943

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

6969
            foreach ($this->getGenerator() as $key => $value) {
7 ✔
6970
                if ($value instanceof self) {
7 ✔
6971
                    $value = $value->toArray(
2 ✔
6972
                        $convertAllArrayyElements,
2 ✔
6973
                        $preserveKeys
2 ✔
6974
                    );
2 ✔
6975
                }
6976

6977
                if ($preserveKeys) {
7 ✔
6978
                    $array[$key] = $value;
6 ✔
6979
                } else {
6980
                    $array[] = $value;
1 ✔
6981
                }
6982
            }
6983

6984
            /* @phpstan-ignore return.type */
6985
            return $array;
7 ✔
6986
        }
6987

6988
        return \iterator_to_array($this->getGenerator(), $preserveKeys);
980 ✔
6989
    }
6990

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

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

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

7035
        return $return;
13 ✔
7036
    }
7037

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

7051
        if ($items === null) {
1 ✔
7052
            $items = $this->toArray();
1 ✔
7053
        }
7054

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

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

7080
        return $return;
1 ✔
7081
    }
7082

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

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

7112
        $this->array = $this->reduce(
13 ✔
7113
            static function ($resultArray, $value, $key) {
13 ✔
7114
                if (!\in_array($value, $resultArray, true)) {
12 ✔
7115
                    $resultArray[] = $value;
12 ✔
7116
                }
7117

7118
                return $resultArray;
12 ✔
7119
            },
13 ✔
7120
            []
13 ✔
7121
        )->toArray();
13 ✔
7122
        $this->generator = null;
13 ✔
7123

7124
        return $this;
13 ✔
7125
    }
7126

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

7143
        // init
7144
        $array = $this->toArray();
11 ✔
7145

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

7157
                return $resultArray;
10 ✔
7158
            },
11 ✔
7159
            []
11 ✔
7160
        );
11 ✔
7161
        $this->generator = null;
11 ✔
7162

7163
        return $this;
11 ✔
7164
    }
7165

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

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

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

7206
        \array_unshift($this->array, ...$args); // @phpstan-ignore assign.propertyType
5 ✔
7207

7208
        return $this;
5 ✔
7209
    }
7210

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

7229
        return true;
1 ✔
7230
    }
7231

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

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

7296
        if ($this->array !== []) {
12 ✔
7297
            if ($recursive === true) {
10 ✔
7298
                if ($userData !== self::ARRAYY_HELPER_WALK) {
5 ✔
7299
                    \array_walk_recursive($this->array, $callable, $userData);
×
7300
                } else {
7301
                    \array_walk_recursive($this->array, $callable);
5 ✔
7302
                }
7303
            } else {
7304
                if ($userData !== self::ARRAYY_HELPER_WALK) {
5 ✔
7305
                    /* @phpstan-ignore argument.type (internal keys are array-key|TKey, the callback contract is TKey) */
7306
                    \array_walk($this->array, $callable, $userData);
×
7307
                } else {
7308
                    /* @phpstan-ignore argument.type */
7309
                    \array_walk($this->array, $callable);
5 ✔
7310
                }
7311
            }
7312
        }
7313

7314
        return $this;
12 ✔
7315
    }
7316

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

7340
                return $accessorValue === $value;
2 ✔
7341
            }
5 ✔
7342
        );
5 ✔
7343
    }
7344

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

7359
        if (\count($array, \COUNT_NORMAL) <= 0) {
4 ✔
7360
            return $object;
1 ✔
7361
        }
7362

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

7371
        return $object;
3 ✔
7372
    }
7373

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

7406
        if ($input === null) {
11 ✔
7407
            $input = $this->getGenerator();
4 ✔
7408
        }
7409

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

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

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

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

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

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

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

7477
        $explodedPath = \explode($this->pathSeparator, $path);
10 ✔
7478
        /* @phpstan-ignore identical.alwaysFalse */
7479
        if ($explodedPath === false) {
10 ✔
7480
            return;
×
7481
        }
7482

7483
        $nextPath = \array_shift($explodedPath);
10 ✔
7484
        if (!isset($currentOffset[$nextPath])) {
10 ✔
7485
            return;
1 ✔
7486
        }
7487

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

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

7519
            if ($return instanceof self) {
1 ✔
7520
                return $return->toArray();
×
7521
            }
7522

7523
            return $return;
1 ✔
7524
        }
7525

7526
        // only use properties / methods that are accessible (and initialized) from here
7527
        if (\is_object($object) && \array_key_exists($keyOrPropertyOrMethod, \get_object_vars($object))) {
4 ✔
7528
            return $object->{$keyOrPropertyOrMethod};
1 ✔
7529
        }
7530

7531
        if (\is_object($object) && \is_callable([$object, $keyOrPropertyOrMethod])) {
3 ✔
7532
            return $object->{$keyOrPropertyOrMethod}();
1 ✔
7533
        }
7534

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

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

7561
        if ($data === null) {
1,340 ✔
7562
            throw new \InvalidArgumentException('Passed value should be a array');
2 ✔
7563
        }
7564

7565
        return $data;
1,338 ✔
7566
    }
7567

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

7585
            return true;
3 ✔
7586
        }
7587

7588
        return false;
1,240 ✔
7589
    }
7590

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

7604
            if ($direction === 'desc') {
10 ✔
7605
                $direction = \SORT_DESC;
2 ✔
7606
            } else {
7607
                $direction = \SORT_ASC;
9 ✔
7608
            }
7609
        }
7610

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

7619
        return $direction;
43 ✔
7620
    }
7621

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

7633
        if (isset($PROPERTY_CACHE[$cacheKey])) {
85 ✔
7634
            $this->optionalProperties = $OPTIONAL_PROPERTY_CACHE[$cacheKey] ?? [];
77 ✔
7635

7636
            return $PROPERTY_CACHE[$cacheKey];
77 ✔
7637
        }
7638

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

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

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

7660
        $this->optionalProperties = $optionalProperties;
19 ✔
7661
        $OPTIONAL_PROPERTY_CACHE[$cacheKey] = $optionalProperties;
19 ✔
7662

7663
        return $PROPERTY_CACHE[$cacheKey] = $properties;
19 ✔
7664
    }
7665

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

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

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

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

7702
        if ($currentPhpDocPropertyAnnotationStyle !== null) {
20 ✔
7703
            $phpDocPropertyAnnotationStyle = $currentPhpDocPropertyAnnotationStyle;
14 ✔
7704
        }
7705

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

7723
        foreach ($arrayShapeItems as $item) {
20 ✔
7724
            $typeName = (string) $item->getKey();
8 ✔
7725
            if ($typeName === '') {
8 ✔
7726
                continue;
×
7727
            }
7728

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

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

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

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

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

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

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

7793
        return $items;
21 ✔
7794
    }
7795

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

7810
        if (\in_array($fqcn, [self::class, ArrayyStrict::class], true)) {
21 ✔
7811
            return true;
20 ✔
7812
        }
7813

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

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

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

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

7844
                $properties[$property->getName()] = TypeCheckPhpDoc::fromReflectionProperty($property);
3 ✔
7845
            }
7846
        } while ($reflector = $reflector->getParentClass());
21 ✔
7847

7848
        return $properties;
21 ✔
7849
    }
7850

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

7858
        if ($reservedProperties !== null) {
21 ✔
7859
            return $reservedProperties;
20 ✔
7860
        }
7861

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

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

7872
        return $reservedProperties;
1 ✔
7873
    }
7874

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

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

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

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

7912
        if (
7913
            \is_scalar($pieces) === true
37 ✔
7914
            ||
7915
            $pieces instanceof \Stringable
37 ✔
7916
        ) {
7917
            return (string) $pieces;
33 ✔
7918
        }
7919

7920
        return '';
8 ✔
7921
    }
7922

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

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

7966
            if ($returnTmp === true) {
15 ✔
7967
                return true;
11 ✔
7968
            }
7969
        }
7970

7971
        return false;
8 ✔
7972
    }
7973

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

7985
        if (!$data) {
117 ✔
7986
            return [];
7 ✔
7987
        }
7988

7989
        if (\is_object($data) === true) {
116 ✔
7990
            if ($data instanceof \ArrayObject) {
109 ✔
7991
                return $data->getArrayCopy();
5 ✔
7992
            }
7993

7994
            if ($data instanceof \Generator) {
105 ✔
7995
                return static::createFromGeneratorImmutable($data)->toArray();
1 ✔
7996
            }
7997

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

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

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

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

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

8021
            return [];
102 ✔
8022
        }
8023

8024
        if (\is_scalar($data)) {
11 ✔
8025
            return [$data];
9 ✔
8026
        }
8027

8028
        return null;
2 ✔
8029
    }
8030

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

8042
        if (
8043
            $this->pathSeparator
29 ✔
8044
            &&
8045
            (string) $key === $key
29 ✔
8046
            &&
8047
            \strpos($key, $this->pathSeparator) !== false
29 ✔
8048
        ) {
8049
            $path = \explode($this->pathSeparator, (string) $key);
×
8050
            // crawl though the keys
8051
            while (\count($path, \COUNT_NORMAL) > 1) {
×
8052
                $key = \array_shift($path);
×
8053

8054
                if (!$this->has($key)) {
×
8055
                    return false;
×
8056
                }
8057

8058
                $this->array = &$this->array[$key];
×
8059
            }
8060

8061
            $key = \array_shift($path);
×
8062
        }
8063

8064
        if ($key === null) {
29 ✔
8065
            return false;
2 ✔
8066
        }
8067

8068
        if (\is_float($key)) {
27 ✔
8069
            return false;
3 ✔
8070
        }
8071

8072
        unset($this->array[$key]);
24 ✔
8073

8074
        return true;
24 ✔
8075
    }
8076

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

8102
        if ($key === null) {
1,177 ✔
8103
            return false;
×
8104
        }
8105

8106
        $this->generatorToArray();
1,177 ✔
8107

8108
        $array = &$this->array;
1,177 ✔
8109

8110
        /**
8111
         * https://github.com/vimeo/psalm/issues/2536
8112
         *
8113
         * @psalm-suppress PossiblyInvalidArgument
8114
         * @psalm-suppress InvalidScalarArgument
8115
         */
8116
        if (
8117
            $this->pathSeparator
1,177 ✔
8118
            &&
8119
            (string) $key === $key
1,177 ✔
8120
            &&
8121
            \strpos($key, $this->pathSeparator) !== false
1,177 ✔
8122
        ) {
8123
            $path = \explode($this->pathSeparator, (string) $key);
9 ✔
8124
            // crawl through the keys
8125
            while (\count($path, \COUNT_NORMAL) > 1) {
9 ✔
8126
                $key = \array_shift($path);
9 ✔
8127

8128
                $array = &$array[$key];
9 ✔
8129
            }
8130

8131
            $key = \array_shift($path);
9 ✔
8132
        }
8133

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

8140
        $array[$key] = $value;
1,177 ✔
8141

8142
        return true;
1,177 ✔
8143
    }
8144

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

8160
        $object = \get_object_vars($object);
6 ✔
8161

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

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

8182
        if ($this->properties === []) {
1,338 ✔
8183
            if (
8184
                $this->checkPropertyTypes === true
1,239 ✔
8185
                ||
8186
                $checkPropertiesInConstructor === true
1,239 ✔
8187
            ) {
8188
                $this->properties = $this->getPropertiesFromPhpDoc();
81 ✔
8189
            }
8190

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

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

8206
        foreach ($data as $key => &$valueInner) {
1,334 ✔
8207
            $this->internalSet(
1,165 ✔
8208
                $key,
1,165 ✔
8209
                $valueInner,
1,165 ✔
8210
                $checkPropertiesInConstructor
1,165 ✔
8211
            );
1,165 ✔
8212
        }
8213
    }
8214

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

8236
        switch ($direction) {
8237
            case 'desc':
18 ✔
8238
            case \SORT_DESC:
8239
                \krsort($elements, $strategy);
6 ✔
8240

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

8248
        return $this;
18 ✔
8249
    }
8250

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

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

8276
        switch ($direction) {
8277
            case 'desc':
24 ✔
8278
            case \SORT_DESC:
8279
                if ($keepKeys) {
13 ✔
8280
                    \arsort($elements, $strategy);
9 ✔
8281
                } else {
8282
                    \rsort($elements, $strategy);
4 ✔
8283
                }
8284

8285
                break;
13 ✔
8286
            case 'asc':
11 ✔
8287
            case \SORT_ASC:
11 ✔
8288
            default:
8289
                if ($keepKeys) {
11 ✔
8290
                    \asort($elements, $strategy);
4 ✔
8291
                } else {
8292
                    \sort($elements, $strategy);
7 ✔
8293
                }
8294
        }
8295

8296
        return $this;
24 ✔
8297
    }
8298

8299
    /**
8300
     * @param array $array
8301
     *
8302
     * @return array
8303
     *
8304
     * @phpstan-param array<array-key, mixed> $array
8305
     * @phpstan-return array<array-key, mixed>
8306
     *
8307
     * @psalm-mutation-free
8308
     */
8309
    private function getArrayRecursiveHelperArrayy(array $array)
8310
    {
8311
        if ($array === []) {
25 ✔
8312
            return [];
×
8313
        }
8314

8315
        \array_walk_recursive(
25 ✔
8316
            $array,
25 ✔
8317
            /**
8318
             * @param array|self $item
8319
             *
8320
             * @return void
8321
             */
8322
            static function (&$item) {
25 ✔
8323
                if ($item instanceof self) {
25 ✔
8324
                    $item = $item->getArray();
1 ✔
8325
                }
8326
            }
25 ✔
8327
        );
25 ✔
8328

8329
        return $array;
25 ✔
8330
    }
8331

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

8350
        if (isset($this->properties[self::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES])) {
180 ✔
8351
            $this->properties[self::ARRAYY_HELPER_TYPES_FOR_ALL_PROPERTIES]->checkType($value);
119 ✔
8352
        } elseif ($key !== null && isset($this->properties[$key])) {
72 ✔
8353
            $this->properties[$key]->checkType($value);
72 ✔
8354
        }
8355
    }
8356
}
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