| Total Complexity | 56 | 
| Total Lines | 666 | 
| Duplicated Lines | 0 % | 
| Changes | 11 | ||
| Bugs | 4 | Features | 3 | 
Complex classes like ArrayView often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ArrayView, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 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  | 
            ||
| 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  | 
            ||
| 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.  | 
            ||
| 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)) { | 
            ||
| 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;  | 
            ||
| 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)) { | 
            ||
| 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)) { | 
            ||
| 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  | 
            ||
| 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  | 
            ||
| 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.  | 
            ||
| 674 | *  | 
            ||
| 675 | * @return bool Returns true if the numeric offset exists in the source, false otherwise.  | 
            ||
| 676 | */  | 
            ||
| 677 | private function numericOffsetExists($offset): bool  | 
            ||
| 698 | }  | 
            ||
| 699 | }  | 
            ||
| 700 |