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

ArrayView::matchWith()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 2
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 2
nc 2
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);
0 ignored issues
show
introduced by
$source is always a sub-type of Smoren\ArrayView\Interfaces\ArrayViewInterface.
Loading history...
159
        $this->parentView = ($source instanceof ArrayViewInterface) ? $source : null;
0 ignored issues
show
introduced by
$source is always a sub-type of Smoren\ArrayView\Interfaces\ArrayViewInterface.
Loading history...
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
     * Filters the elements in the view based on a predicate function.
185
     *
186
     * ##### Example
187
     * ```php
188
     * $source = [1, 2, 3, 4, 5, 6];
189
     * $view = ArrayView::toView($source);
190
     *
191
     * $filtered = $view->filter(fn ($x) => $x % 2 === 0);
192
     * $filtered->toArray(); // [2, 4, 6]
193
     *
194
     * $filtered[':'] = [20, 40, 60];
195
     * $filtered->toArray(); // [20, 40, 60]
196
     * $source; // [1, 20, 3, 40, 5, 60]
197
     * ```
198
     *
199
     * @param callable(T, int): bool $predicate Function that returns a boolean value for each element.
200
     *
201
     * @return ArrayMaskView<T> A new view with elements that satisfy the predicate.
202
     */
203
    public function filter(callable $predicate): ArrayViewInterface
204
    {
205
        return $this->is($predicate)->select($this);
206
    }
207
208
    /**
209
     * Checks if all elements in the view satisfy a given predicate function.
210
     *
211
     * ##### Example
212
     * ```php
213
     * $source = [1, 2, 3, 4, 5, 6];
214
     * $view = ArrayView::toView($source);
215
     *
216
     * $mask = $view->is(fn ($x) => $x % 2 === 0);
217
     * $mask->getValue(); // [false, true, false, true, false, true]
218
     *
219
     * $view->subview($mask)->toArray(); // [2, 4, 6]
220
     * $view[$mask]; // [2, 4, 6]
221
     *
222
     * $view[$mask] = [20, 40, 60];
223
     * $source; // [1, 20, 3, 40, 5, 60]
224
     * ```
225
     *
226
     * @param callable(T, int): bool $predicate Function that returns a boolean value for each element.
227
     *
228
     * @return MaskSelector Boolean mask for selecting elements that satisfy the predicate.
229
     *
230
     * @see ArrayViewInterface::match() Full synonim.
231
     */
232
    public function is(callable $predicate): MaskSelectorInterface
233
    {
234
        $data = $this->toArray();
235
        return new MaskSelector(array_map($predicate, $data, array_keys($data)));
236
    }
237
238
    /**
239
     * Checks if all elements in the view satisfy a given predicate function.
240
     *
241
     * ##### Example
242
     * ```php
243
     * $source = [1, 2, 3, 4, 5, 6];
244
     * $view = ArrayView::toView($source);
245
     *
246
     * $mask = $view->is(fn ($x) => $x % 2 === 0);
247
     * $mask->getValue(); // [false, true, false, true, false, true]
248
     *
249
     * $view->subview($mask)->toArray(); // [2, 4, 6]
250
     * $view[$mask]; // [2, 4, 6]
251
     *
252
     * $view[$mask] = [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 MaskSelector Boolean mask for selecting elements that satisfy the predicate.
259
     *
260
     * @see ArrayView::match() Full synonim.
261
     */
262
    public function match(callable $predicate): MaskSelectorInterface
263
    {
264
        return $this->is($predicate);
265
    }
266
267
    /**
268
     * Compares the elements of the current ArrayView instance with another array or ArrayView
269
     * using the provided comparator function.
270
     *
271
     * @template U The type of the elements in the array for comparison with.
272
     *
273
     * @param array<U>|ArrayViewInterface<U> $data The array or ArrayView to compare to.
274
     * @param callable(T, U, int): bool $comparator Function that determines the comparison logic between the elements.
275
     *
276
     * @return MaskSelectorInterface A MaskSelector instance representing the results of the element comparisons.
277
     *
278
     * @throws ValueError if the $data is not sequential array.
279
     * @throws SizeError if size of $data not equals to size of the view.
280
     *
281
     * @see ArrayView::is() Full synonim.
282
     */
283
    public function matchWith($data, callable $comparator): MaskSelectorInterface
284
    {
285
        $data = $data instanceof ArrayViewInterface ? $data->toArray() : $data;
286
        return new MaskSelector(array_map($comparator, $this->toArray(), $data, array_keys($data)));
287
    }
288
289
    /**
290
     * Returns a subview of this view based on a selector or string slice.
291
     *
292
     * ##### Example (using selector objects)
293
     * ```php
294
     * $source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
295
     *
296
     * $subview = ArrayView::toView($source)
297
     *     ->subview(new SliceSelector('::2'))                          // [1, 3, 5, 7, 9]
298
     *     ->subview(new MaskSelector([true, false, true, true, true])) // [1, 5, 7, 9]
299
     *     ->subview(new IndexListSelector([0, 1, 2]))                  // [1, 5, 7]
300
     *     ->subview(new SliceSelector('1:'));                          // [5, 7]
301
     *
302
     * $subview[':'] = [55, 77];
303
     * print_r($source); // [1, 2, 3, 4, 55, 6, 77, 8, 9, 10]
304
     * ```
305
     *
306
     * ##### Example (using short objects)
307
     * ```php
308
     * $source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
309
     *
310
     * $subview = ArrayView::toView($source)
311
     *     ->subview('::2')                           // [1, 3, 5, 7, 9]
312
     *     ->subview([true, false, true, true, true]) // [1, 5, 7, 9]
313
     *     ->subview([0, 1, 2])                       // [1, 5, 7]
314
     *     ->subview('1:');                           // [5, 7]
315
     *
316
     * $subview[':'] = [55, 77];
317
     * print_r($source); // [1, 2, 3, 4, 55, 6, 77, 8, 9, 10]
318
     * ```
319
     *
320
     * ##### Readonly example
321
     * ```php
322
     * $source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
323
     * $subview = ArrayView::toView($source)->subview('::2');
324
     *
325
     * $subview[':']; // [1, 3, 5, 7, 9]
326
     * $subview[':'] = [11, 33, 55, 77, 99]; // throws ReadonlyError
327
     * $subview[0] = [11]; // throws ReadonlyError
328
     * ```
329
     *
330
     * @template S of string|array<int|bool>|ArrayViewInterface<int|bool>|ArraySelectorInterface Selector type.
331
     *
332
     * @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...
333
     * @param bool|null $readonly Flag indicating if the subview should be read-only.
334
     *
335
     * @return ArrayViewInterface<T> A new view representing the subview of this view.
336
     *
337
     * @throws IndexError if the selector is IndexListSelector and some indexes are out of range.
338
     * @throws SizeError if the selector is MaskSelector and size of the mask not equals to size of the view.
339
     * @throws KeyError if the selector is not valid (e.g. non-sequential array).
340
     */
341
    public function subview($selector, bool $readonly = null): ArrayViewInterface
342
    {
343
        return $this->toSelector($selector)->select($this, $readonly);
344
    }
345
346
    /**
347
     * Applies a transformation function to each element in the view.
348
     *
349
     * ##### Example
350
     * ```php
351
     * $source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
352
     * $subview = ArrayView::toView($source)->subview('::2'); // [1, 3, 5, 7, 9]
353
     *
354
     * $subview->apply(fn ($x) => $x * 10);
355
     *
356
     * $subview->toArray(); // [10, 30, 50, 70, 90]
357
     * $source; // [10, 2, 30, 4, 50, 6, 70, 8, 90, 10]
358
     * ```
359
     *
360
     * @param callable(T, int): T $mapper Function to transform each element.
361
     *
362
     * @return ArrayView<T> this view.
363
     */
364
    public function apply(callable $mapper): self
365
    {
366
        $size = \count($this);
367
        for ($i = 0; $i < $size; $i++) {
368
            /** @var T $item */
369
            $item = $this[$i];
370
            $this[$i] = $mapper($item, $i);
371
        }
372
        return $this;
373
    }
374
375
    /**
376
     * Sets new values for the elements in the view.
377
     *
378
     * ##### Example
379
     * ```php
380
     * $source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
381
     * $subview = ArrayView::toView($source)->subview('::2'); // [1, 3, 5, 7, 9]
382
     *
383
     * $data = [9, 27, 45, 63, 81];
384
     *
385
     * $subview->applyWith($data, fn ($lhs, $rhs) => $lhs * $rhs);
386
     * $subview->toArray(); // [10, 30, 50, 70, 90]
387
     *
388
     * $source; // [10, 2, 30, 4, 50, 6, 70, 8, 90, 10]
389
     * ```
390
     *
391
     * @template U Type of $data items.
392
     *
393
     * @param array<U>|ArrayViewInterface<U> $data
394
     * @param callable(T, U, int): T $mapper
395
     *
396
     * @return ArrayView<T> this view.
397
     *
398
     * @throws ValueError if the $data is not sequential array.
399
     * @throws SizeError if size of $data not equals to size of the view.
400
     */
401
    public function applyWith($data, callable $mapper): self
402
    {
403
        $this->checkSequential($data);
404
405
        [$dataSize, $thisSize] = [\count($data), \count($this)];
406
        if ($dataSize !== $thisSize) {
407
            throw new SizeError("Length of values array not equal to view length ({$dataSize} != {$thisSize}).");
408
        }
409
410
        $dataView = ArrayView::toView($data);
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
            $this[$i] = $mapper($lhs, $rhs, $i);
419
        }
420
421
        return $this;
422
    }
423
424
    /**
425
     * Sets new values for the elements in the view.
426
     *
427
     * ##### Example
428
     * ```php
429
     * $source = [1, 2, 3, 4, 5];
430
     * $subview = ArrayView::toView($source)->subview('::2'); // [1, 3, 5]
431
     *
432
     * $subview->set([11, 33, 55]);
433
     * $subview->toArray(); // [11, 33, 55]
434
     *
435
     * $source; // [11, 2, 33, 4, 55]
436
     * ```
437
     *
438
     * @param array<T>|ArrayViewInterface<T>|T $newValues The new values to set.
439
     *
440
     * @return ArrayView<T> this view.
441
     *
442
     * @throws ValueError if the $newValues is not sequential array.
443
     * @throws SizeError if size of $newValues not equals to size of the view.
444
     */
445
    public function set($newValues): self
446
    {
447
        $this->checkSequential($newValues);
448
449
        if (!\is_array($newValues) && !($newValues instanceof ArrayViewInterface)) {
0 ignored issues
show
introduced by
The condition is_array($newValues) is always false.
Loading history...
introduced by
$newValues is always a sub-type of Smoren\ArrayView\Interfaces\ArrayViewInterface.
Loading history...
450
            $size = \count($this);
451
            for ($i = 0; $i < $size; $i++) {
452
                $this[$i] = $newValues;
453
            }
454
            return $this;
455
        }
456
457
        [$dataSize, $thisSize] = [\count($newValues), \count($this)];
458
        if ($dataSize !== $thisSize) {
459
            throw new SizeError("Length of values array not equal to view length ({$dataSize} != {$thisSize}).");
460
        }
461
462
        $size = \count($this);
463
464
        for ($i = 0; $i < $size; $i++) {
465
            $this[$i] = $newValues[$i];
466
        }
467
468
        return $this;
469
    }
470
471
    /**
472
     * Return iterator to iterate the view elements.
473
     *
474
     * ##### Example
475
     * ```php
476
     * $source = [1, 2, 3, 4, 5];
477
     * $subview = ArrayView::toView($source)->subview('::2'); // [1, 3, 5]
478
     *
479
     * foreach ($subview as $item) {
480
     *     // 1, 3, 5
481
     * }
482
     *
483
     * print_r([...$subview]); // [1, 3, 5]
484
     * ```
485
     *
486
     * @return \Generator<int, T>
487
     */
488
    public function getIterator(): \Generator
489
    {
490
        $size = \count($this);
491
        for ($i = 0; $i < $size; $i++) {
492
            /** @var T $item */
493
            $item = $this[$i];
494
            yield $item;
495
        }
496
    }
497
498
    /**
499
     * Return true if view is readonly, otherwise false.
500
     *
501
     * ##### Example
502
     * ```php
503
     * $source = [1, 2, 3, 4, 5];
504
     *
505
     * $readonlyView = ArrayView::toView($source, true);
506
     * $readonlyView->isReadonly(); // true
507
     *
508
     * $readonlySubview = ArrayView::toView($source)->subview('::2', true);
509
     * $readonlySubview->isReadonly(); // true
510
     *
511
     * $view = ArrayView::toView($source);
512
     * $view->isReadonly(); // false
513
     *
514
     * $subview = ArrayView::toView($source)->subview('::2');
515
     * $subview->isReadonly(); // false
516
     * ```
517
     *
518
     * @return bool
519
     */
520
    public function isReadonly(): bool
521
    {
522
        return $this->readonly;
523
    }
524
525
    /**
526
     * Return size of the view.
527
     *
528
     * ##### Example
529
     * ```php
530
     * $source = [1, 2, 3, 4, 5];
531
     *
532
     * $subview = ArrayView::toView($source)->subview('::2'); // [1, 3, 5]
533
     * count($subview); // 3
534
     * ```
535
     *
536
     * @return int
537
     */
538
    public function count(): int
539
    {
540
        return $this->getParentSize();
541
    }
542
543
    /**
544
     * Get the size of the parent view or source array.
545
     *
546
     * @return int The size of the parent view or source array.
547
     */
548
    protected function getParentSize(): int
549
    {
550
        return ($this->parentView !== null)
551
            ? \count($this->parentView)
552
            : \count($this->source);
553
    }
554
555
    /**
556
     * Check if the given source array is sequential (indexed from 0 to n-1).
557
     *
558
     * If the array is not sequential, a ValueError is thrown indicating that
559
     * a view cannot be created for a non-sequential array.
560
     *
561
     * @param mixed $source The source array to check for sequential indexing.
562
     *
563
     * @return void
564
     *
565
     * @throws ValueError if the source array is not sequential.
566
     */
567
    protected function checkSequential($source): void
568
    {
569
        if (is_array($source) && !Util::isArraySequential($source)) {
570
            throw new ValueError('Cannot create view for non-sequential array.');
571
        }
572
    }
573
574
    /**
575
     * Convert the given index to a valid index within the source array.
576
     *
577
     * @param int $i The index to convert.
578
     *
579
     * @return int The converted index within the source array.
580
     *
581
     * @throws IndexError if the index is out of range and $throwError is true.
582
     */
583
    protected function convertIndex(int $i): int
584
    {
585
        return Util::normalizeIndex($i, \count($this->source));
586
    }
587
588
    /**
589
     * Check if a numeric offset exists in the source array.
590
     *
591
     * @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...
592
     *
593
     * @return bool Returns true if the numeric offset exists in the source, false otherwise.
594
     */
595
    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...
596
    {
597
        // Non-string must be integer
598
        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...
599
            return false;
600
        }
601
602
        // Numeric string must be integer
603
        if (!\is_integer($offset + 0)) {
604
            return false;
605
        }
606
607
        try {
608
            $index = $this->convertIndex(intval($offset));
609
        } catch (IndexError $e) {
610
            return false;
611
        }
612
613
        return \is_array($this->source)
614
            ? \array_key_exists($index, $this->source)
615
            : $this->source->offsetExists($index);
616
    }
617
}
618