Passed
Pull Request — master (#28)
by Smoren
02:13
created

ArrayView::mapWith()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
c 0
b 0
f 0
dl 0
loc 28
rs 9.4222
cc 5
nc 9
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Smoren\ArrayView\Views;
6
7
use Smoren\ArrayView\Exceptions\IndexError;
8
use Smoren\ArrayView\Exceptions\KeyError;
9
use Smoren\ArrayView\Exceptions\SizeError;
10
use Smoren\ArrayView\Exceptions\ReadonlyError;
11
use Smoren\ArrayView\Exceptions\ValueError;
12
use Smoren\ArrayView\Interfaces\ArraySelectorInterface;
13
use Smoren\ArrayView\Interfaces\ArrayViewInterface;
14
use Smoren\ArrayView\Interfaces\MaskSelectorInterface;
15
use Smoren\ArrayView\Selectors\MaskSelector;
16
use Smoren\ArrayView\Traits\ArrayViewAccessTrait;
17
use Smoren\ArrayView\Util;
18
19
/**
20
 * Class representing a view of an array or another array view
21
 * with additional methods for filtering, mapping, and transforming the data.
22
 *
23
 * ```php
24
 * $source = [1, 2, 3, 4, 5];
25
 * $view = ArrayView::toView($source);
26
 * ```
27
 *
28
 * @template T Type of array source elements.
29
 *
30
 * @implements ArrayViewInterface<T>
31
 */
32
class ArrayView implements ArrayViewInterface
33
{
34
    /**
35
     * @use ArrayViewAccessTrait<T, string|array<int|bool>|ArrayViewInterface<int|bool>|ArraySelectorInterface>
36
     *
37
     * for array access methods.
38
     */
39
    use ArrayViewAccessTrait;
40
41
    /**
42
     * @var array<T>|ArrayViewInterface<T> The source array or view.
43
     */
44
    protected $source;
45
    /**
46
     * @var bool Flag indicating if the view is readonly.
47
     */
48
    protected bool $readonly;
49
    /**
50
     * @var ArrayViewInterface<T>|null The parent view of the current view.
51
     */
52
    protected ?ArrayViewInterface $parentView;
53
54
    /**
55
     * Creates an ArrayView instance from the given source array or ArrayView.
56
     *
57
     * * If the source is not an ArrayView, a new ArrayView is created with the provided source.
58
     * * If the source is an ArrayView and the `readonly` parameter is specified as `true`,
59
     * a new readonly ArrayView is created.
60
     * * If the source is an ArrayView and it is already readonly, the same ArrayView is returned.
61
     *
62
     * ##### Example
63
     * ```php
64
     * $source = [1, 2, 3, 4, 5];
65
     * $view = ArrayView::toView($source);
66
     *
67
     * $view[0]; // 1
68
     * $view['1::2']; // [2, 4]
69
     * $view['1::2'] = [22, 44];
70
     *
71
     * $view->toArray(); // [1, 22, 3, 44, 5]
72
     * $source; // [1, 22, 3, 44, 5]
73
     * ```
74
     *
75
     * ##### Readonly example
76
     * ```php
77
     * $source = [1, 2, 3, 4, 5];
78
     * $view = ArrayView::toView($source, true);
79
     *
80
     * $view['1::2']; // [2, 4]
81
     * $view['1::2'] = [22, 44]; // throws ReadonlyError
82
     * $view[0] = 11; // throws ReadonlyError
83
     * ```
84
     *
85
     * @param array<T>|ArrayViewInterface<T> $source The source array or ArrayView to create a view from.
86
     * @param bool|null $readonly Optional flag to indicate whether the view should be readonly.
87
     *
88
     * @return ArrayViewInterface<T> An ArrayView instance based on the source array or ArrayView.
89
     *
90
     * @throws ValueError if the array is not sequential.
91
     * @throws ReadonlyError if the source is readonly and trying to create a non-readonly view.
92
     */
93
    public static function toView(&$source, ?bool $readonly = null): ArrayViewInterface
94
    {
95
        if (!($source instanceof ArrayViewInterface)) {
96
            return new ArrayView($source, $readonly);
97
        }
98
99
        if (!$source->isReadonly() && $readonly) {
100
            return new ArrayView($source, $readonly);
101
        }
102
103
        return $source;
104
    }
105
106
    /**
107
     * {@inheritDoc}
108
     *
109
     * ##### Example:
110
     * ```php
111
     * $source = [1, 2, 3, 4, 5];
112
     * $view = ArrayView::toUnlinkedView($source);
113
     *
114
     * $view[0]; // 1
115
     * $view['1::2']; // [2, 4]
116
     * $view['1::2'] = [22, 44];
117
     *
118
     * $view->toArray(); // [1, 22, 3, 44, 5]
119
     * $source; // [1, 2, 3, 4, 5]
120
     * ```
121
     *
122
     * ##### Readonly example:
123
     * ```php
124
     * $source = [1, 2, 3, 4, 5];
125
     * $view = ArrayView::toUnlinkedView($source, true);
126
     *
127
     * $view['1::2']; // [2, 4]
128
     * $view['1::2'] = [22, 44]; // throws ReadonlyError
129
     * $view[0] = 11; // throws ReadonlyError
130
     * ```
131
     */
132
    public static function toUnlinkedView($source, ?bool $readonly = null): ArrayViewInterface
133
    {
134
        return static::toView($source, $readonly);
135
    }
136
137
    /**
138
     * Constructor to create a new ArrayView.
139
     *
140
     * * If the source is not an ArrayView, a new ArrayView is created with the provided source.
141
     * * If the source is an ArrayView and the `readonly` parameter is specified as `true`,
142
     * a new readonly ArrayView is created.
143
     * * If the source is an ArrayView and it is already readonly, the same ArrayView is returned.
144
     *
145
     * @param array<T>|ArrayViewInterface<T> $source The source array or view.
146
     * @param bool|null $readonly Flag indicating if the view is readonly.
147
     *
148
     * @throws ValueError if the array is not sequential.
149
     * @throws ReadonlyError if the source is readonly and trying to create a non-readonly view.
150
     *
151
     * @see ArrayView::toView() for creating views.
152
     */
153
    public function __construct(&$source, ?bool $readonly = null)
154
    {
155
        $this->checkSequential($source);
156
157
        $this->source = &$source;
158
        $this->readonly = $readonly ?? (($source instanceof ArrayViewInterface) ? $source->isReadonly() : false);
159
        $this->parentView = ($source instanceof ArrayViewInterface) ? $source : null;
160
161
        if (($source instanceof ArrayViewInterface) && $source->isReadonly() && !$this->isReadonly()) {
162
            throw new ReadonlyError("Cannot create non-readonly view for readonly source.");
163
        }
164
    }
165
166
    /**
167
     * Returns the array representation of the view.
168
     *
169
     * ##### Example
170
     * ```php
171
     * $source = [1, 2, 3, 4, 5];
172
     * $view = ArrayView::toView($source);
173
     * $view->toArray(); // [1, 2, 3, 4, 5]
174
     * ```
175
     *
176
     * @return array<T> The array representation of the view.
177
     */
178
    public function toArray(): array
179
    {
180
        return [...$this];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array($this) returns the type array<integer,Smoren\ArrayView\Views\ArrayView> which is incompatible with the documented return type Smoren\ArrayView\Views\T[].
Loading history...
181
    }
182
183
    /**
184
     * Returns a subview of this view based on a selector or string slice.
185
     *
186
     * ##### Example (using selector objects)
187
     * ```php
188
     * $source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
189
     *
190
     * $subview = ArrayView::toView($source)
191
     *     ->subview(new SliceSelector('::2'))                          // [1, 3, 5, 7, 9]
192
     *     ->subview(new MaskSelector([true, false, true, true, true])) // [1, 5, 7, 9]
193
     *     ->subview(new IndexListSelector([0, 1, 2]))                  // [1, 5, 7]
194
     *     ->subview(new SliceSelector('1:'));                          // [5, 7]
195
     *
196
     * $subview[':'] = [55, 77];
197
     * print_r($source); // [1, 2, 3, 4, 55, 6, 77, 8, 9, 10]
198
     * ```
199
     *
200
     * ##### Example (using short objects)
201
     * ```php
202
     * $source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
203
     *
204
     * $subview = ArrayView::toView($source)
205
     *     ->subview('::2')                           // [1, 3, 5, 7, 9]
206
     *     ->subview([true, false, true, true, true]) // [1, 5, 7, 9]
207
     *     ->subview([0, 1, 2])                       // [1, 5, 7]
208
     *     ->subview('1:');                           // [5, 7]
209
     *
210
     * $subview[':'] = [55, 77];
211
     * print_r($source); // [1, 2, 3, 4, 55, 6, 77, 8, 9, 10]
212
     * ```
213
     *
214
     * ##### Readonly example
215
     * ```php
216
     * $source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
217
     * $subview = ArrayView::toView($source)->subview('::2');
218
     *
219
     * $subview[':']; // [1, 3, 5, 7, 9]
220
     * $subview[':'] = [11, 33, 55, 77, 99]; // throws ReadonlyError
221
     * $subview[0] = [11]; // throws ReadonlyError
222
     * ```
223
     *
224
     * @template S of string|array<int|bool>|ArrayViewInterface<int|bool>|ArraySelectorInterface Selector type.
225
     *
226
     * @param S $selector The selector or string to filter the subview.
0 ignored issues
show
Bug introduced by
The type Smoren\ArrayView\Views\S was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
227
     * @param bool|null $readonly Flag indicating if the subview should be read-only.
228
     *
229
     * @return ArrayViewInterface<T> A new view representing the subview of this view.
230
     *
231
     * @throws IndexError if the selector is IndexListSelector and some indexes are out of range.
232
     * @throws SizeError if the selector is MaskSelector and size of the mask not equals to size of the view.
233
     * @throws KeyError if the selector is not valid (e.g. non-sequential array).
234
     */
235
    public function subview($selector, bool $readonly = null): ArrayViewInterface
236
    {
237
        return $this->toSelector($selector)->select($this, $readonly);
238
    }
239
240
    /**
241
     * Filters the elements in the view based on a predicate function.
242
     *
243
     * ##### Example
244
     * ```php
245
     * $source = [1, 2, 3, 4, 5, 6];
246
     * $view = ArrayView::toView($source);
247
     *
248
     * $filtered = $view->filter(fn ($x) => $x % 2 === 0);
249
     * $filtered->toArray(); // [2, 4, 6]
250
     *
251
     * $filtered[':'] = [20, 40, 60];
252
     * $filtered->toArray(); // [20, 40, 60]
253
     * $source; // [1, 20, 3, 40, 5, 60]
254
     * ```
255
     *
256
     * @param callable(T, int): bool $predicate Function that returns a boolean value for each element.
257
     *
258
     * @return ArrayMaskView<T> A new view with elements that satisfy the predicate.
259
     */
260
    public function filter(callable $predicate): ArrayViewInterface
261
    {
262
        return $this->is($predicate)->select($this);
263
    }
264
265
    /**
266
     * Checks if all elements in the view satisfy a given predicate function.
267
     *
268
     * ##### Example
269
     * ```php
270
     * $source = [1, 2, 3, 4, 5, 6];
271
     * $view = ArrayView::toView($source);
272
     *
273
     * $mask = $view->is(fn ($x) => $x % 2 === 0);
274
     * $mask->getValue(); // [false, true, false, true, false, true]
275
     *
276
     * $view->subview($mask)->toArray(); // [2, 4, 6]
277
     * $view[$mask]; // [2, 4, 6]
278
     *
279
     * $view[$mask] = [20, 40, 60];
280
     * $source; // [1, 20, 3, 40, 5, 60]
281
     * ```
282
     *
283
     * @param callable(T, int): bool $predicate Function that returns a boolean value for each element.
284
     *
285
     * @return MaskSelector Boolean mask for selecting elements that satisfy the predicate.
286
     *
287
     * @see ArrayViewInterface::match() Full synonim.
288
     */
289
    public function is(callable $predicate): MaskSelectorInterface
290
    {
291
        $data = $this->toArray();
292
        return new MaskSelector(array_map($predicate, $data, array_keys($data)));
293
    }
294
295
    /**
296
     * Checks if all elements in the view satisfy a given predicate function.
297
     *
298
     * ##### Example
299
     * ```php
300
     * $source = [1, 2, 3, 4, 5, 6];
301
     * $view = ArrayView::toView($source);
302
     *
303
     * $mask = $view->is(fn ($x) => $x % 2 === 0);
304
     * $mask->getValue(); // [false, true, false, true, false, true]
305
     *
306
     * $view->subview($mask)->toArray(); // [2, 4, 6]
307
     * $view[$mask]; // [2, 4, 6]
308
     *
309
     * $view[$mask] = [20, 40, 60];
310
     * $source; // [1, 20, 3, 40, 5, 60]
311
     * ```
312
     *
313
     * @param callable(T, int): bool $predicate Function that returns a boolean value for each element.
314
     *
315
     * @return MaskSelector Boolean mask for selecting elements that satisfy the predicate.
316
     *
317
     * @see ArrayView::match() Full synonim.
318
     */
319
    public function match(callable $predicate): MaskSelectorInterface
320
    {
321
        return $this->is($predicate);
322
    }
323
324
    /**
325
     * Compares the elements of the current ArrayView instance with another array or ArrayView
326
     * using the provided comparator function.
327
     *
328
     * @template U The type of the elements in the array for comparison with.
329
     *
330
     * @param array<U>|ArrayViewInterface<U>|U $data The array or ArrayView to compare to.
331
     * @param callable(T, U, int): bool $comparator Function that determines the comparison logic between the elements.
332
     *
333
     * @return MaskSelectorInterface A MaskSelector instance representing the results of the element comparisons.
334
     *
335
     * @throws ValueError if the $data is not sequential array.
336
     * @throws SizeError if size of $data not equals to size of the view.
337
     *
338
     * @see ArrayView::is() Full synonim.
339
     */
340
    public function matchWith($data, callable $comparator): MaskSelectorInterface
341
    {
342
        $this->checkSequential($data); // TODO test
343
344
        if ($data instanceof ArrayViewInterface) {
345
            $data = $data->toArray();
346
        } elseif (!\is_array($data)) {
0 ignored issues
show
introduced by
The condition is_array($data) is always true.
Loading history...
347
            $data = \array_fill(0, \count($this), $data);
348
        }
349
350
        [$dataSize, $thisSize] = [\count($data), \count($this)];
351
        if ($dataSize !== $thisSize) {
352
            throw new SizeError("Length of values array not equal to view length ({$dataSize} != {$thisSize}).");
353
        }
354
355
        $data = $data instanceof ArrayViewInterface ? $data->toArray() : $data;
0 ignored issues
show
introduced by
$data is never a sub-type of Smoren\ArrayView\Interfaces\ArrayViewInterface.
Loading history...
356
        return new MaskSelector(array_map($comparator, $this->toArray(), $data, array_keys($data)));
357
    }
358
359
    /**
360
     * Transforms each element of the array using the given callback function.
361
     *
362
     * The callback function receives two parameters: the current element of the array and its index.
363
     *
364
     * @param callable(T, int): T $mapper Function to transform each element.
365
     *
366
     * @return array<T> New array with transformed elements of this view.
367
     */
368
    public function map(callable $mapper): array
369
    {
370
        $result = [];
371
        $size = \count($this);
372
        for ($i = 0; $i < $size; $i++) {
373
            /** @var T $item */
374
            $item = $this[$i];
375
            $result[$i] = $mapper($item, $i);
376
        }
377
        return $result;
378
    }
379
380
    /**
381
     * Transforms each pair of elements from the current array view and the provided data array using the given
382
     * callback function.
383
     *
384
     * The callback function receives three parameters: the current element of the current array view,
385
     * the corresponding element of the data array, and the index.
386
     *
387
     * @template U The type rhs of a binary operation.
388
     *
389
     * @param array<U>|ArrayViewInterface<U>|U $data The rhs values for a binary operation.
390
     * @param callable(T, U, int): T $mapper Function to transform each pair of elements.
391
     *
392
     * @return array<mixed> New array with transformed elements of this view.
393
     */
394
    public function mapWith($data, callable $mapper): array
395
    {
396
        $this->checkSequential($data);
397
398
        if ($data instanceof ArrayViewInterface) {
399
            $data = $data->toArray();
400
        } elseif (!\is_array($data)) {
0 ignored issues
show
introduced by
The condition is_array($data) is always true.
Loading history...
401
            $data = \array_fill(0, \count($this), $data);
402
        }
403
404
        [$dataSize, $thisSize] = [\count($data), \count($this)];
405
        if ($dataSize !== $thisSize) {
406
            throw new SizeError("Length of values array not equal to view length ({$dataSize} != {$thisSize}).");
407
        }
408
409
        $dataView = ArrayView::toView($data);
410
        $result = [];
411
412
        $size = \count($this);
413
        for ($i = 0; $i < $size; $i++) {
414
            /** @var T $lhs */
415
            $lhs = $this[$i];
416
            /** @var U $rhs */
417
            $rhs = $dataView[$i];
418
            $result[$i] = $mapper($lhs, $rhs, $i);
419
        }
420
421
        return $result;
422
    }
423
424
    /**
425
     * Applies a transformation function to each element in the view.
426
     *
427
     * ##### Example
428
     * ```php
429
     * $source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
430
     * $subview = ArrayView::toView($source)->subview('::2'); // [1, 3, 5, 7, 9]
431
     *
432
     * $subview->apply(fn ($x) => $x * 10);
433
     *
434
     * $subview->toArray(); // [10, 30, 50, 70, 90]
435
     * $source; // [10, 2, 30, 4, 50, 6, 70, 8, 90, 10]
436
     * ```
437
     *
438
     * @param callable(T, int): T $mapper Function to transform each element.
439
     *
440
     * @return ArrayView<T> this view.
441
     */
442
    public function apply(callable $mapper): self
443
    {
444
        $size = \count($this);
445
        for ($i = 0; $i < $size; $i++) {
446
            /** @var T $item */
447
            $item = $this[$i];
448
            $this[$i] = $mapper($item, $i);
449
        }
450
        return $this;
451
    }
452
453
    /**
454
     * Sets new values for the elements in the view.
455
     *
456
     * ##### Example
457
     * ```php
458
     * $source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
459
     * $subview = ArrayView::toView($source)->subview('::2'); // [1, 3, 5, 7, 9]
460
     *
461
     * $data = [9, 27, 45, 63, 81];
462
     *
463
     * $subview->applyWith($data, fn ($lhs, $rhs) => $lhs * $rhs);
464
     * $subview->toArray(); // [10, 30, 50, 70, 90]
465
     *
466
     * $source; // [10, 2, 30, 4, 50, 6, 70, 8, 90, 10]
467
     * ```
468
     *
469
     * @template U Type of $data items.
470
     *
471
     * @param array<U>|ArrayViewInterface<U> $data
472
     * @param callable(T, U, int): T $mapper
473
     *
474
     * @return ArrayView<T> this view.
475
     *
476
     * @throws ValueError if the $data is not sequential array.
477
     * @throws SizeError if size of $data not equals to size of the view.
478
     */
479
    public function applyWith($data, callable $mapper): self
480
    {
481
        $this->checkSequential($data);
482
483
        [$dataSize, $thisSize] = [\count($data), \count($this)];
484
        if ($dataSize !== $thisSize) {
485
            throw new SizeError("Length of values array not equal to view length ({$dataSize} != {$thisSize}).");
486
        }
487
488
        $dataView = ArrayView::toView($data);
489
490
        $size = \count($this);
491
        for ($i = 0; $i < $size; $i++) {
492
            /** @var T $lhs */
493
            $lhs = $this[$i];
494
            /** @var U $rhs */
495
            $rhs = $dataView[$i];
496
            $this[$i] = $mapper($lhs, $rhs, $i);
497
        }
498
499
        return $this;
500
    }
501
502
    /**
503
     * Sets new values for the elements in the view.
504
     *
505
     * ##### Example
506
     * ```php
507
     * $source = [1, 2, 3, 4, 5];
508
     * $subview = ArrayView::toView($source)->subview('::2'); // [1, 3, 5]
509
     *
510
     * $subview->set([11, 33, 55]);
511
     * $subview->toArray(); // [11, 33, 55]
512
     *
513
     * $source; // [11, 2, 33, 4, 55]
514
     * ```
515
     *
516
     * @param array<T>|ArrayViewInterface<T>|T $newValues The new values to set.
517
     *
518
     * @return ArrayView<T> this view.
519
     *
520
     * @throws ValueError if the $newValues is not sequential array.
521
     * @throws SizeError if size of $newValues not equals to size of the view.
522
     */
523
    public function set($newValues): self
524
    {
525
        $this->checkSequential($newValues);
526
527
        if (!\is_array($newValues) && !($newValues instanceof ArrayViewInterface)) {
0 ignored issues
show
introduced by
$newValues is always a sub-type of Smoren\ArrayView\Interfaces\ArrayViewInterface.
Loading history...
528
            $size = \count($this);
529
            for ($i = 0; $i < $size; $i++) {
530
                $this[$i] = $newValues;
531
            }
532
            return $this;
533
        }
534
535
        [$dataSize, $thisSize] = [\count($newValues), \count($this)];
536
        if ($dataSize !== $thisSize) {
537
            throw new SizeError("Length of values array not equal to view length ({$dataSize} != {$thisSize}).");
538
        }
539
540
        $size = \count($this);
541
542
        for ($i = 0; $i < $size; $i++) {
543
            $this[$i] = $newValues[$i];
544
        }
545
546
        return $this;
547
    }
548
549
    /**
550
     * Return iterator to iterate the view elements.
551
     *
552
     * ##### Example
553
     * ```php
554
     * $source = [1, 2, 3, 4, 5];
555
     * $subview = ArrayView::toView($source)->subview('::2'); // [1, 3, 5]
556
     *
557
     * foreach ($subview as $item) {
558
     *     // 1, 3, 5
559
     * }
560
     *
561
     * print_r([...$subview]); // [1, 3, 5]
562
     * ```
563
     *
564
     * @return \Generator<int, T>
565
     */
566
    public function getIterator(): \Generator
567
    {
568
        $size = \count($this);
569
        for ($i = 0; $i < $size; $i++) {
570
            /** @var T $item */
571
            $item = $this[$i];
572
            yield $item;
573
        }
574
    }
575
576
    /**
577
     * Return true if view is readonly, otherwise false.
578
     *
579
     * ##### Example
580
     * ```php
581
     * $source = [1, 2, 3, 4, 5];
582
     *
583
     * $readonlyView = ArrayView::toView($source, true);
584
     * $readonlyView->isReadonly(); // true
585
     *
586
     * $readonlySubview = ArrayView::toView($source)->subview('::2', true);
587
     * $readonlySubview->isReadonly(); // true
588
     *
589
     * $view = ArrayView::toView($source);
590
     * $view->isReadonly(); // false
591
     *
592
     * $subview = ArrayView::toView($source)->subview('::2');
593
     * $subview->isReadonly(); // false
594
     * ```
595
     *
596
     * @return bool
597
     */
598
    public function isReadonly(): bool
599
    {
600
        return $this->readonly;
601
    }
602
603
    /**
604
     * Return size of the view.
605
     *
606
     * ##### Example
607
     * ```php
608
     * $source = [1, 2, 3, 4, 5];
609
     *
610
     * $subview = ArrayView::toView($source)->subview('::2'); // [1, 3, 5]
611
     * count($subview); // 3
612
     * ```
613
     *
614
     * @return int
615
     */
616
    public function count(): int
617
    {
618
        return $this->getParentSize();
619
    }
620
621
    /**
622
     * Get the size of the parent view or source array.
623
     *
624
     * @return int The size of the parent view or source array.
625
     */
626
    protected function getParentSize(): int
627
    {
628
        return ($this->parentView !== null)
629
            ? \count($this->parentView)
630
            : \count($this->source);
631
    }
632
633
    /**
634
     * Check if the given source array is sequential (indexed from 0 to n-1).
635
     *
636
     * If the array is not sequential, a ValueError is thrown indicating that
637
     * a view cannot be created for a non-sequential array.
638
     *
639
     * @param mixed $source The source array to check for sequential indexing.
640
     *
641
     * @return void
642
     *
643
     * @throws ValueError if the source array is not sequential.
644
     */
645
    protected function checkSequential($source): void
646
    {
647
        if ($source instanceof ArrayViewInterface) {
648
            return;
649
        }
650
651
        if (\is_array($source) && !Util::isArraySequential($source)) {
652
            throw new ValueError('Cannot create view for non-sequential array.');
653
        }
654
    }
655
656
    /**
657
     * Convert the given index to a valid index within the source array.
658
     *
659
     * @param int $i The index to convert.
660
     *
661
     * @return int The converted index within the source array.
662
     *
663
     * @throws IndexError if the index is out of range and $throwError is true.
664
     */
665
    protected function convertIndex(int $i): int
666
    {
667
        return Util::normalizeIndex($i, \count($this->source));
668
    }
669
670
    /**
671
     * Check if a numeric offset exists in the source array.
672
     *
673
     * @param numeric $offset The numeric offset to check.
0 ignored issues
show
Bug introduced by
The type Smoren\ArrayView\Views\numeric was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
674
     *
675
     * @return bool Returns true if the numeric offset exists in the source, false otherwise.
676
     */
677
    private function numericOffsetExists($offset): bool
0 ignored issues
show
Unused Code introduced by
The method numericOffsetExists() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
678
    {
679
        // Non-string must be integer
680
        if (!\is_string($offset) && !\is_int($offset)) {
0 ignored issues
show
introduced by
The condition is_int($offset) is always false.
Loading history...
introduced by
The condition is_string($offset) is always false.
Loading history...
681
            return false;
682
        }
683
684
        // Numeric string must be integer
685
        if (!\is_integer($offset + 0)) {
686
            return false;
687
        }
688
689
        try {
690
            $index = $this->convertIndex(intval($offset));
691
        } catch (IndexError $e) {
692
            return false;
693
        }
694
695
        return \is_array($this->source)
696
            ? \array_key_exists($index, $this->source)
697
            : $this->source->offsetExists($index);
698
    }
699
}
700