MutableArrayStream   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 279
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 67
c 1
b 0
f 0
dl 0
loc 279
ccs 86
cts 86
cp 1
rs 9.28
wmc 39

22 Methods

Rating   Name   Duplication   Size   Complexity  
A matchOne() 0 9 3
A map() 0 12 2
A skip() 0 5 1
A limit() 0 5 1
A mapKey() 0 12 2
A sort() 0 17 4
A toArray() 0 3 2
A filter() 0 5 1
A forEach() 0 4 2
A distinct() 0 5 1
A __construct() 0 3 1
A matchAll() 0 9 3
A flatMap() 0 3 1
A first() 0 9 2
A concat() 0 12 3
A reduce() 0 6 3
A collect() 0 7 2
A rewind() 0 3 1
A current() 0 4 1
A next() 0 3 1
A valid() 0 3 1
A key() 0 4 1
1
<?php
2
3
namespace Bdf\Collection\Stream;
4
5
use Bdf\Collection\Stream\Accumulator\AccumulatorInterface;
6
use Bdf\Collection\Stream\Collector\CollectorInterface;
7
use Bdf\Collection\Util\Optional;
8
use Bdf\Collection\Util\OptionalInterface;
9
use Iterator;
10
use function array_filter;
11
use function array_merge;
12
use function array_reduce;
13
use function array_replace;
14
use function array_slice;
15
use function array_values;
16
use function asort;
17
use function current;
18
use function key;
19
use function reset;
20
use function sort;
21
use function uasort;
22
use function usort;
23
24
/**
25
 * Stream for array using native PHP array methods
26
 * The inner array will be modified by transformation calls
27
 * So unlike other streams, the transformation methods will be called directly, and returns this
28
 *
29
 * This implementation will reduce the overhead on small arrays, but remove the laziness of streams.
30
 * For big arrays, normal streams are advised
31
 *
32
 * Some methods have a different behavior :
33
 * - distinct() : The hash functor or custom class hash are not used for comparison
34
 * - first()    : Not optimized in sort() context (all the array will be sorted, instead of find the min value)
35
 * - mapKey()   : May failed if the function return an invalid key
36
 *
37
 * @template T
38
 * @template K as array-key
39
 *
40
 * @implements StreamInterface<T, K>
41
 * @implements Iterator<K, T>
42
 */
43
final class MutableArrayStream implements Iterator, StreamInterface
44
{
45
    /**
46
     * @var array<K, T>
47
     */
48
    private $data;
49
50
51
    /**
52
     * MutableArrayStream constructor.
53
     *
54
     * @param array<K, T> $data
55
     */
56 28
    public function __construct(array $data)
57
    {
58 28
        $this->data = $data;
59 28
    }
60
61
    /**
62
     * {@inheritdoc}
63
     *
64
     * @psalm-param callable(T,K=):R $transformer
65
     * @psalm-return MutableArrayStream<R, K>
66
     *
67
     * @template R
68
     */
69 2
    public function map(callable $transformer): StreamInterface
70
    {
71 2
        $newData = [];
72
73 2
        foreach ($this->data as $k => $v) {
74 2
            $newData[$k] = $transformer($v, $k);
75
        }
76
77
        /** @var MutableArrayStream<R, K> $this */
78 2
        $this->data = $newData;
79
80 2
        return $this;
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     *
86
     * @psalm-param callable(T,K):R $function
87
     * @psalm-return MutableArrayStream<T, R>
88
     *
89
     * @template R as array-key
90
     */
91 1
    public function mapKey(callable $function): StreamInterface
92
    {
93 1
        $newData = [];
94
95 1
        foreach ($this->data as $k => $v) {
96 1
            $newData[$function($v, $k)] = $v;
97
        }
98
99
        /** @var MutableArrayStream<T, R> $this */
100 1
        $this->data = $newData;
101
102 1
        return $this;
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108 1
    public function filter(callable $predicate): StreamInterface
109
    {
110 1
        $this->data = array_filter($this->data, $predicate, ARRAY_FILTER_USE_BOTH);
111
112 1
        return $this;
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118 1
    public function distinct(callable $hashFunction = null): StreamInterface
119
    {
120 1
        $this->data = array_unique($this->data, SORT_REGULAR);
121
122 1
        return $this;
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128 4
    public function sort(callable $comparator = null, bool $preserveKeys = false): StreamInterface
129
    {
130 4
        if ($comparator) {
131 2
            if ($preserveKeys) {
132 1
                uasort($this->data, $comparator);
133
            } else {
134
                /** @var MutableArrayStream<T, int> $this */
135 2
                usort($this->data, $comparator);
136
            }
137 2
        } elseif ($preserveKeys) {
138 1
            asort($this->data);
139
        } else {
140
            /** @var MutableArrayStream<T, int> $this */
141 1
            sort($this->data);
142
        }
143
144 4
        return $this;
145
    }
146
147
    /**
148
     * {@inheritdoc}
149
     */
150 3
    public function concat(StreamInterface $stream, bool $preserveKeys = true): StreamInterface
151
    {
152 3
        if ($stream instanceof MutableArrayStream) {
153 2
            $this->data = $preserveKeys
154 1
                ? array_replace($this->data, $stream->data)
155 1
                : array_merge(array_values($this->data), array_values($stream->data))
156
            ;
157
158 2
            return $this;
159
        }
160
161 1
        return new ConcatStream([$this, $stream], $preserveKeys);
162
    }
163
164
    /**
165
     * {@inheritdoc}
166
     *
167
     * @psalm-suppress InvalidArgument
168
     */
169 2
    public function flatMap(callable $transformer, bool $preserveKeys = false): StreamInterface
170
    {
171 2
        return new FlatMapStream($this, $transformer, $preserveKeys);
172
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177 1
    public function skip(int $count): StreamInterface
178
    {
179 1
        $this->data = array_slice($this->data, $count, null, true);
180
181 1
        return $this;
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187 1
    public function limit(int $count, int $offset = 0): StreamInterface
188
    {
189 1
        $this->data = array_slice($this->data, $offset, $count, true);
190
191 1
        return $this;
192
    }
193
194
    /**
195
     * {@inheritdoc}
196
     */
197 1
    public function forEach(callable $consumer): void
198
    {
199 1
        foreach ($this->data as $k => $v) {
200 1
            $consumer($v, $k);
201
        }
202 1
    }
203
204
    /**
205
     * {@inheritdoc}
206
     *
207
     * @template PK as bool
208
     * @psalm-param PK $preserveKeys
209
     * @psalm-return (PK is true ? array<K, T> : list<T>)
210
     */
211 14
    public function toArray(bool $preserveKeys = true): array
212
    {
213 14
        return $preserveKeys ? $this->data : array_values($this->data);
214
    }
215
216
    /**
217
     * {@inheritdoc}
218
     */
219 1
    public function first(): OptionalInterface
220
    {
221 1
        if (empty($this->data)) {
222 1
            return Optional::empty();
223
        }
224
225 1
        reset($this->data);
226
227 1
        return Optional::nullable(current($this->data));
228
    }
229
230
    /**
231
     * {@inheritdoc}
232
     */
233 5
    public function reduce(callable $accumulator, $initial = null)
234
    {
235 5
        return array_reduce(
236 5
            $this->data,
237 5
            $accumulator,
238 5
            $initial === null && $accumulator instanceof AccumulatorInterface ? $accumulator->initial() : $initial
239
        );
240
    }
241
242
    /**
243
     * {@inheritdoc}
244
     */
245 1
    public function collect(CollectorInterface $collector)
246
    {
247 1
        foreach ($this->data as $key => $item) {
248 1
            $collector->aggregate($item, $key);
249
        }
250
251 1
        return $collector->finalize();
252
    }
253
254
    /**
255
     * {@inheritdoc}
256
     */
257 1
    public function matchAll(callable $predicate): bool
258
    {
259 1
        foreach ($this->data as $key => $item) {
260 1
            if (!$predicate($item, $key)) {
261 1
                return false;
262
            }
263
        }
264
265 1
        return true;
266
    }
267
268
    /**
269
     * {@inheritdoc}
270
     */
271 1
    public function matchOne(callable $predicate): bool
272
    {
273 1
        foreach ($this->data as $key => $item) {
274 1
            if ($predicate($item, $key)) {
275 1
                return true;
276
            }
277
        }
278
279 1
        return false;
280
    }
281
282
    /**
283
     * {@inheritdoc}
284
     */
285
    #[\ReturnTypeWillChange]
286 4
    public function current()
287
    {
288 4
        return current($this->data);
289
    }
290
291
    /**
292
     * {@inheritdoc}
293
     */
294 4
    public function next(): void
295
    {
296 4
        next($this->data);
297 4
    }
298
299
    /**
300
     * {@inheritdoc}
301
     */
302
    #[\ReturnTypeWillChange]
303 4
    public function key()
304
    {
305 4
        return key($this->data);
306
    }
307
308
    /**
309
     * {@inheritdoc}
310
     */
311 4
    public function valid(): bool
312
    {
313 4
        return key($this->data) !== null;
314
    }
315
316
    /**
317
     * {@inheritdoc}
318
     */
319 4
    public function rewind(): void
320
    {
321 4
        reset($this->data);
322 4
    }
323
}
324