Passed
Push — master ( f33296...7aa466 )
by Smoren
01:56 queued 10s
created

ArrayView::checkSequential()   A

Complexity

Conditions 3
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 3
nc 2
nop 1
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\SizeError;
9
use Smoren\ArrayView\Exceptions\ReadonlyError;
10
use Smoren\ArrayView\Exceptions\ValueError;
11
use Smoren\ArrayView\Interfaces\ArrayViewInterface;
12
use Smoren\ArrayView\Interfaces\MaskSelectorInterface;
13
use Smoren\ArrayView\Selectors\MaskSelector;
14
use Smoren\ArrayView\Selectors\SliceSelector;
15
use Smoren\ArrayView\Traits\ArrayViewAccessTrait;
16
use Smoren\ArrayView\Util;
17
18
/**
19
 * Class representing a view of an array or another array view
20
 * with additional methods for filtering, mapping, and transforming the data.
21
 *
22
 * @template T
23
 *
24
 * @implements ArrayViewInterface<T>
25
 */
26
class ArrayView implements ArrayViewInterface
27
{
28
    /**
29
     * @use ArrayViewAccessTrait<T> for array access methods.
30
     */
31
    use ArrayViewAccessTrait;
32
33
    /**
34
     * @var array<T>|ArrayViewInterface<T> The source array or view.
35
     */
36
    protected $source;
37
    /**
38
     * @var bool Flag indicating if the view is readonly.
39
     */
40
    protected bool $readonly;
41
    /**
42
     * @var ArrayViewInterface<T>|null The parent view of the current view.
43
     */
44
    protected ?ArrayViewInterface $parentView;
45
46
    /**
47
     * {@inheritDoc}
48
     */
49
    public static function toView(&$source, ?bool $readonly = null): ArrayViewInterface
50
    {
51
        if (!($source instanceof ArrayViewInterface)) {
52
            return new ArrayView($source, $readonly);
53
        }
54
55
        if (!$source->isReadonly() && $readonly) {
56
            return new ArrayView($source, $readonly);
57
        }
58
59
        return $source;
60
    }
61
62
    /**
63
     * {@inheritDoc}
64
     */
65
    public static function toUnlinkedView($source, ?bool $readonly = null): ArrayViewInterface
66
    {
67
        return static::toView($source, $readonly);
68
    }
69
70
    /**
71
     * Constructor to create a new ArrayView.
72
     *
73
     * @param array<T>|ArrayViewInterface<T> $source The source array or view.
74
     * @param bool|null $readonly Flag indicating if the view is readonly.
75
     *
76
     * @throws ValueError if the array is not sequential.
77
     * @throws ReadonlyError if the source is readonly and trying to create a non-readonly view.
78
     */
79
    public function __construct(&$source, ?bool $readonly = null)
80
    {
81
        $this->checkSequential($source);
82
83
        $this->source = &$source;
84
        $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...
85
        $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...
86
87
        if (($source instanceof ArrayViewInterface) && $source->isReadonly() && !$this->isReadonly()) {
88
            throw new ReadonlyError("Cannot create non-readonly view for readonly source.");
89
        }
90
    }
91
92
    /**
93
     * {@inheritDoc}
94
     */
95
    public function toArray(): array
96
    {
97
        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 return type mandated by Smoren\ArrayView\Interfa...iewInterface::toArray() of Smoren\ArrayView\Interfaces\T[].

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
98
    }
99
100
    /**
101
     * {@inheritDoc}
102
     */
103
    public function filter(callable $predicate): ArrayViewInterface
104
    {
105
        return $this->is($predicate)->select($this);
106
    }
107
108
    /**
109
     * {@inheritDoc}
110
     */
111
    public function is(callable $predicate): MaskSelectorInterface
112
    {
113
        $data = $this->toArray();
114
        return new MaskSelector(array_map($predicate, $data, array_keys($data)));
115
    }
116
117
    /**
118
     * {@inheritDoc}
119
     */
120
    public function subview($selector, bool $readonly = null): ArrayViewInterface
121
    {
122
        return is_string($selector)
123
            ? (new SliceSelector($selector))->select($this, $readonly)
124
            : $selector->select($this, $readonly);
125
    }
126
127
    /**
128
     * @return ArrayView<T>
129
     *
130
     * {@inheritDoc}
131
     */
132
    public function apply(callable $mapper): self
133
    {
134
        $size = \count($this);
135
        for ($i = 0; $i < $size; $i++) {
136
            /** @var T $item */
137
            $item = $this[$i];
138
            $this[$i] = $mapper($item, $i);
139
        }
140
        return $this;
141
    }
142
143
    /**
144
     * @template U
145
     *
146
     * @param array<U>|ArrayViewInterface<U> $data
147
     * @param callable(T, U, int): T $mapper
148
     *
149
     * @return ArrayView<T>
150
     *
151
     * {@inheritDoc}
152
     */
153
    public function applyWith($data, callable $mapper): self
154
    {
155
        $this->checkSequential($data);
156
157
        [$dataSize, $thisSize] = [\count($data), \count($this)];
158
        if ($dataSize !== $thisSize) {
159
            throw new SizeError("Length of values array not equal to view length ({$dataSize} != {$thisSize}).");
160
        }
161
162
        $dataView = ArrayView::toView($data);
163
164
        $size = \count($this);
165
        for ($i = 0; $i < $size; $i++) {
166
            /** @var T $lhs */
167
            $lhs = $this[$i];
168
            /** @var U $rhs */
169
            $rhs = $dataView[$i];
170
            $this[$i] = $mapper($lhs, $rhs, $i);
171
        }
172
173
        return $this;
174
    }
175
176
    /**
177
     * {@inheritDoc}
178
     *
179
     * @return ArrayView<T> this view.
180
     *
181
     * @throws SizeError if the length of newValues array is not equal to the length of the view.
182
     */
183
    public function set($newValues): self
184
    {
185
        $this->checkSequential($newValues);
186
187
        if (!\is_array($newValues) && !($newValues instanceof ArrayViewInterface)) {
188
            $size = \count($this);
189
            for ($i = 0; $i < $size; $i++) {
190
                $this[$i] = $newValues;
191
            }
192
            return $this;
193
        }
194
195
        [$dataSize, $thisSize] = [\count($newValues), \count($this)];
196
        if ($dataSize !== $thisSize) {
197
            throw new SizeError("Length of values array not equal to view length ({$dataSize} != {$thisSize}).");
198
        }
199
200
        $newValuesView = ArrayView::toView($newValues);
201
202
        $size = \count($this);
203
        for ($i = 0; $i < $size; $i++) {
204
            $this[$i] = $newValuesView[$i];
205
        }
206
207
        return $this;
208
    }
209
210
    /**
211
     * {@inheritDoc}
212
     */
213
    public function getIterator(): \Generator
214
    {
215
        $size = \count($this);
216
        for ($i = 0; $i < $size; $i++) {
217
            /** @var T $item */
218
            $item = $this[$i];
219
            yield $item;
220
        }
221
    }
222
223
    /**
224
     * {@inheritDoc}
225
     */
226
    public function isReadonly(): bool
227
    {
228
        return $this->readonly;
229
    }
230
231
    /**
232
     * {@inheritDoc}
233
     */
234
    public function count(): int
235
    {
236
        return $this->getParentSize();
237
    }
238
239
    /**
240
     * Get the size of the parent view or source array.
241
     *
242
     * @return int The size of the parent view or source array.
243
     */
244
    protected function getParentSize(): int
245
    {
246
        return ($this->parentView !== null)
247
            ? \count($this->parentView)
248
            : \count($this->source);
249
    }
250
251
    /**
252
     * Check if the given source array is sequential (indexed from 0 to n-1).
253
     *
254
     * If the array is not sequential, a ValueError is thrown indicating that
255
     * a view cannot be created for a non-sequential array.
256
     *
257
     * @param mixed $source The source array to check for sequential indexing.
258
     *
259
     * @return void
260
     *
261
     * @throws ValueError if the source array is not sequential.
262
     */
263
    protected function checkSequential($source): void
264
    {
265
        if (is_array($source) && !Util::isArraySequential($source)) {
266
            throw new ValueError('Cannot create view for non-sequential array.');
267
        }
268
    }
269
270
    /**
271
     * Convert the given index to a valid index within the source array.
272
     *
273
     * @param int $i The index to convert.
274
     *
275
     * @return int The converted index within the source array.
276
     *
277
     * @throws IndexError if the index is out of range and $throwError is true.
278
     */
279
    protected function convertIndex(int $i): int
280
    {
281
        return Util::normalizeIndex($i, \count($this->source));
282
    }
283
284
    /**
285
     * Check if a numeric offset exists in the source array.
286
     *
287
     * @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...
288
     *
289
     * @return bool Returns true if the numeric offset exists in the source, false otherwise.
290
     */
291
    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...
292
    {
293
        if (!\is_string($offset) && \is_numeric($offset) && (\is_nan($offset) || \is_infinite($offset))) {
0 ignored issues
show
introduced by
The condition is_numeric($offset) is always false.
Loading history...
introduced by
The condition is_string($offset) is always false.
Loading history...
294
            return false;
295
        }
296
297
        try {
298
            $index = $this->convertIndex(intval($offset));
299
        } catch (IndexError $e) {
300
            return false;
301
        }
302
        return \is_array($this->source)
303
            ? \array_key_exists($index, $this->source)
304
            : $this->source->offsetExists($index);
305
    }
306
}
307