Total Complexity | 48 |
Total Lines | 435 |
Duplicated Lines | 0 % |
Coverage | 100% |
Changes | 4 | ||
Bugs | 0 | Features | 0 |
Complex classes like Sequence 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 Sequence, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
16 | final class Sequence implements \Countable |
||
17 | { |
||
18 | private array $values; |
||
19 | private ?int $size; |
||
20 | |||
21 | 182 | public function __construct(...$values) |
|
24 | 182 | } |
|
25 | |||
26 | 3 | public static function of(...$values): self |
|
32 | } |
||
33 | |||
34 | 128 | public function size(): int |
|
35 | { |
||
36 | 128 | return $this->size ?? $this->size = \count($this->values); |
|
37 | } |
||
38 | |||
39 | /** |
||
40 | * {@inheritdoc} |
||
41 | */ |
||
42 | 1 | public function count(): int |
|
43 | { |
||
44 | 1 | return $this->size(); |
|
45 | } |
||
46 | |||
47 | 104 | public function toArray(): array |
|
48 | { |
||
49 | 104 | return $this->values; |
|
50 | } |
||
51 | |||
52 | /** |
||
53 | * Return the element at the given index |
||
54 | * |
||
55 | * @throws OutOfBoundException |
||
56 | * |
||
57 | * @return mixed |
||
58 | */ |
||
59 | 26 | public function get(int $index) |
|
60 | { |
||
61 | 26 | if (!$this->has($index)) { |
|
62 | 2 | throw new OutOfBoundException; |
|
63 | } |
||
64 | |||
65 | 24 | return $this->values[$index]; |
|
66 | } |
||
67 | |||
68 | /** |
||
69 | * Check the index exist |
||
70 | */ |
||
71 | 31 | public function has(int $index): bool |
|
72 | { |
||
73 | 31 | return \array_key_exists($index, $this->values); |
|
74 | } |
||
75 | |||
76 | /** |
||
77 | * Return the diff between this sequence and another |
||
78 | */ |
||
79 | 4 | public function diff(self $seq): self |
|
80 | { |
||
81 | return $this->filter(static function($value) use ($seq): bool { |
||
82 | 4 | return !$seq->contains($value); |
|
83 | 4 | }); |
|
84 | } |
||
85 | |||
86 | /** |
||
87 | * Remove all duplicates from the sequence |
||
88 | */ |
||
89 | 35 | public function distinct(): self |
|
90 | { |
||
91 | 35 | return $this->reduce( |
|
92 | 35 | new self, |
|
93 | static function(self $values, $value): self { |
||
94 | 27 | if ($values->contains($value)) { |
|
95 | 4 | return $values; |
|
96 | } |
||
97 | |||
98 | 27 | return $values->add($value); |
|
99 | 35 | } |
|
100 | ); |
||
101 | } |
||
102 | |||
103 | /** |
||
104 | * Remove the n first elements |
||
105 | */ |
||
106 | 5 | public function drop(int $size): self |
|
107 | { |
||
108 | 5 | $self = new self; |
|
109 | 5 | $self->values = \array_slice($this->values, $size); |
|
110 | |||
111 | 5 | return $self; |
|
112 | } |
||
113 | |||
114 | /** |
||
115 | * Remove the n last elements |
||
116 | */ |
||
117 | 2 | public function dropEnd(int $size): self |
|
118 | { |
||
119 | 2 | $self = new self; |
|
120 | 2 | $self->values = \array_slice($this->values, 0, $this->size() - $size); |
|
121 | |||
122 | 2 | return $self; |
|
123 | } |
||
124 | |||
125 | /** |
||
126 | * Check if the two sequences are equal |
||
127 | */ |
||
128 | 8 | public function equals(self $seq): bool |
|
129 | { |
||
130 | 8 | return $this->values === $seq->toArray(); |
|
131 | } |
||
132 | |||
133 | /** |
||
134 | * Return all elements that satisfy the given predicate |
||
135 | * |
||
136 | * @param callable(mixed): bool $predicate |
||
137 | */ |
||
138 | 20 | public function filter(callable $predicate): self |
|
139 | { |
||
140 | 20 | return new self(...\array_filter( |
|
141 | 20 | $this->values, |
|
142 | 20 | $predicate |
|
143 | )); |
||
144 | } |
||
145 | |||
146 | /** |
||
147 | * Apply the given function to all elements of the sequence |
||
148 | * |
||
149 | * @param callable(mixed): void $function |
||
150 | */ |
||
151 | 54 | public function foreach(callable $function): void |
|
152 | { |
||
153 | 54 | foreach ($this->values as $value) { |
|
154 | 43 | $function($value); |
|
155 | } |
||
156 | 53 | } |
|
157 | |||
158 | /** |
||
159 | * Return a new map of pairs grouped by keys determined with the given |
||
160 | * discriminator function |
||
161 | * |
||
162 | * @param callable(mixed) $discriminator |
||
163 | * |
||
164 | * @return Map<mixed, self> |
||
165 | */ |
||
166 | 2 | public function groupBy(callable $discriminator): Map |
|
167 | { |
||
168 | 2 | if ($this->size() === 0) { |
|
169 | 1 | throw new GroupEmptySequenceException; |
|
170 | } |
||
171 | |||
172 | 1 | $map = null; |
|
173 | |||
174 | 1 | foreach ($this->values as $value) { |
|
175 | 1 | $key = $discriminator($value); |
|
176 | |||
177 | 1 | if ($map === null) { |
|
178 | 1 | $map = new Map( |
|
179 | 1 | Type::determine($key), |
|
180 | 1 | self::class |
|
181 | ); |
||
182 | } |
||
183 | |||
184 | 1 | if ($map->contains($key)) { |
|
185 | 1 | $map = $map->put( |
|
186 | 1 | $key, |
|
187 | 1 | $map->get($key)->add($value) |
|
188 | ); |
||
189 | } else { |
||
190 | 1 | $map = $map->put($key, new self($value)); |
|
191 | } |
||
192 | } |
||
193 | |||
194 | 1 | return $map; |
|
1 ignored issue
–
show
|
|||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Return the first element |
||
199 | * |
||
200 | * @return mixed |
||
201 | */ |
||
202 | 4 | public function first() |
|
203 | { |
||
204 | 4 | if ($this->size() === 0) { |
|
205 | 1 | throw new OutOfBoundException; |
|
206 | } |
||
207 | |||
208 | 3 | return $this->values[0]; |
|
209 | } |
||
210 | |||
211 | /** |
||
212 | * Return the last element |
||
213 | * |
||
214 | * @return mixed |
||
215 | */ |
||
216 | 4 | public function last() |
|
217 | { |
||
218 | 4 | if ($this->size() === 0) { |
|
219 | 1 | throw new OutOfBoundException; |
|
220 | } |
||
221 | |||
222 | 3 | return $this->values[$this->size() - 1]; |
|
223 | } |
||
224 | |||
225 | /** |
||
226 | * Check if the sequence contains the given element |
||
227 | * |
||
228 | * @param mixed $element |
||
229 | */ |
||
230 | 75 | public function contains($element): bool |
|
231 | { |
||
232 | 75 | return \in_array($element, $this->values, true); |
|
233 | } |
||
234 | |||
235 | /** |
||
236 | * Return the index for the given element |
||
237 | * |
||
238 | * @param mixed $element |
||
239 | * |
||
240 | * @throws ElementNotFoundException |
||
241 | */ |
||
242 | 19 | public function indexOf($element): int |
|
243 | { |
||
244 | 19 | $index = \array_search($element, $this->values, true); |
|
245 | |||
246 | 19 | if ($index === false) { |
|
247 | 1 | throw new ElementNotFoundException; |
|
248 | } |
||
249 | |||
250 | 18 | return $index; |
|
251 | } |
||
252 | |||
253 | /** |
||
254 | * Return the list of indices |
||
255 | * |
||
256 | * @return Stream<int> |
||
257 | */ |
||
258 | 4 | public function indices(): Stream |
|
259 | { |
||
260 | 4 | if ($this->size() === 0) { |
|
261 | 2 | return Stream::of('int'); |
|
262 | } |
||
263 | |||
264 | 2 | return Stream::of('int', ...\range(0, $this->size() - 1)); |
|
265 | } |
||
266 | |||
267 | /** |
||
268 | * Return a new sequence by applying the given function to all elements |
||
269 | * |
||
270 | * @param callable(mixed) $function |
||
271 | */ |
||
272 | 4 | public function map(callable $function): self |
|
278 | } |
||
279 | |||
280 | /** |
||
281 | * Pad the sequence to a defined size with the given element |
||
282 | * |
||
283 | * @param mixed $element |
||
284 | */ |
||
285 | 2 | public function pad(int $size, $element): self |
|
286 | { |
||
287 | 2 | $self = new self; |
|
288 | 2 | $self->values = \array_pad($this->values, $size, $element); |
|
289 | |||
290 | 2 | return $self; |
|
291 | } |
||
292 | |||
293 | /** |
||
294 | * Return a sequence of 2 sequences partitioned according to the given predicate |
||
295 | * |
||
296 | * @param callable(mixed): bool $predicate |
||
297 | * |
||
298 | * @return Map<bool, self> |
||
299 | */ |
||
300 | 1 | public function partition(callable $predicate): Map |
|
301 | { |
||
302 | 1 | $truthy = []; |
|
303 | 1 | $falsy = []; |
|
304 | |||
305 | 1 | foreach ($this->values as $value) { |
|
306 | 1 | if ($predicate($value) === true) { |
|
307 | 1 | $truthy[] = $value; |
|
308 | } else { |
||
309 | 1 | $falsy[] = $value; |
|
310 | } |
||
311 | } |
||
312 | |||
313 | 1 | $true = new self; |
|
314 | 1 | $true->values = $truthy; |
|
315 | 1 | $false = new self; |
|
316 | 1 | $false->values = $falsy; |
|
317 | |||
318 | 1 | return Map::of('bool', self::class) |
|
319 | 1 | (true, $true) |
|
320 | 1 | (false, $false); |
|
321 | } |
||
322 | |||
323 | 13 | public function slice(int $from, int $until): self |
|
324 | { |
||
325 | 13 | $self = new self; |
|
326 | 13 | $self->values = \array_slice( |
|
327 | 13 | $this->values, |
|
328 | 13 | $from, |
|
329 | 13 | $until - $from |
|
330 | ); |
||
331 | |||
332 | 13 | return $self; |
|
333 | } |
||
334 | |||
335 | /** |
||
336 | * Split the sequence in a sequence of 2 sequences splitted at the given position |
||
337 | * |
||
338 | * @throws OutOfBoundException |
||
339 | * |
||
340 | * @return Stream<self> |
||
341 | */ |
||
342 | 2 | public function splitAt(int $index): Stream |
|
343 | { |
||
344 | 2 | return (new Stream(self::class)) |
|
345 | 2 | ->add($this->slice(0, $index)) |
|
346 | 2 | ->add($this->slice($index, $this->size())); |
|
347 | } |
||
348 | |||
349 | /** |
||
350 | * Return a sequence with the n first elements |
||
351 | */ |
||
352 | 5 | public function take(int $size): self |
|
355 | } |
||
356 | |||
357 | /** |
||
358 | * Return a sequence with the n last elements |
||
359 | */ |
||
360 | 2 | public function takeEnd(int $size): self |
|
361 | { |
||
362 | 2 | return $this->slice($this->size() - $size, $this->size()); |
|
363 | } |
||
364 | |||
365 | /** |
||
366 | * Append the given sequence to the current one |
||
367 | */ |
||
368 | 7 | public function append(self $seq): self |
|
369 | { |
||
370 | 7 | $self = new self; |
|
371 | 7 | $self->values = \array_merge($this->values, $seq->toArray()); |
|
372 | |||
373 | 7 | return $self; |
|
374 | } |
||
375 | |||
376 | /** |
||
377 | * Return a sequence with all elements from the current one that exist |
||
378 | * in the given one |
||
379 | */ |
||
380 | 13 | public function intersect(self $seq): self |
|
384 | 13 | }); |
|
385 | } |
||
386 | |||
387 | /** |
||
388 | * Concatenate all elements with the given separator |
||
389 | */ |
||
390 | 11 | public function join(string $separator): Str |
|
391 | { |
||
392 | 11 | return new Str(\implode($separator, $this->values)); |
|
393 | } |
||
394 | |||
395 | /** |
||
396 | * Add the given element at the end of the sequence |
||
397 | * |
||
398 | * @param mixed $element |
||
399 | */ |
||
400 | 115 | public function add($element): self |
|
401 | { |
||
402 | 115 | $self = clone $this; |
|
403 | 115 | $self->values[] = $element; |
|
404 | 115 | $self->size = $this->size() + 1; |
|
405 | |||
406 | 115 | return $self; |
|
407 | } |
||
408 | |||
409 | /** |
||
410 | * Sort the sequence in a different order |
||
411 | * |
||
412 | * @param callable(mixed, mixed): int $function |
||
413 | */ |
||
414 | 3 | public function sort(callable $function): self |
|
415 | { |
||
416 | 3 | $self = clone $this; |
|
417 | 3 | \usort($self->values, $function); |
|
418 | |||
419 | 3 | return $self; |
|
420 | } |
||
421 | |||
422 | /** |
||
423 | * Reduce the sequence to a single value |
||
424 | * |
||
425 | * @param mixed $carry |
||
426 | * @param callable(mixed, mixed) $reducer |
||
427 | * |
||
428 | * @return mixed |
||
429 | */ |
||
430 | 42 | public function reduce($carry, callable $reducer) |
|
431 | { |
||
432 | 42 | return \array_reduce($this->values, $reducer, $carry); |
|
433 | } |
||
434 | |||
435 | /** |
||
436 | * Return the same sequence but in reverse order |
||
437 | * |
||
438 | * @return self |
||
439 | */ |
||
440 | 3 | public function reverse(): self |
|
446 | } |
||
447 | |||
448 | 4 | public function empty(): bool |
|
449 | { |
||
450 | 4 | return !$this->has(0); |
|
451 | } |
||
452 | } |
||
453 |