Passed
Pull Request — master (#62)
by Sergei
19:21 queued 04:24
created

ArrayCollection   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 243
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 80
dl 0
loc 243
rs 8.8
c 4
b 0
f 0
wmc 45

18 Methods

Rating   Name   Duplication   Size   Complexity  
A offsetSet() 0 3 1
A isMergable() 0 3 2
A withModifiers() 0 5 1
A __construct() 0 4 2
A withAddedModifier() 0 3 1
B mergeWith() 0 29 9
C merge() 0 38 12
A getModifiers() 0 3 1
A count() 0 3 1
A performArray() 0 12 4
A getIterator() 0 3 1
A withData() 0 5 1
A offsetGet() 0 3 1
A withModifier() 0 3 1
A offsetUnset() 0 3 1
A withAddedModifiers() 0 5 1
A toArray() 0 12 4
A offsetExists() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like ArrayCollection 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 ArrayCollection, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Arrays\Collection;
6
7
use ArrayAccess;
8
use ArrayIterator;
9
use Countable;
10
use IteratorAggregate;
11
use Yiisoft\Arrays\Collection\Modifier\ModifierInterface\AfterMergeModifierInterface;
12
use Yiisoft\Arrays\Collection\Modifier\ModifierInterface\BeforeMergeModifierInterface;
13
use Yiisoft\Arrays\Collection\Modifier\ModifierInterface\DataModifierInterface;
14
use Yiisoft\Arrays\Collection\Modifier\ModifierInterface\ModifierInterface;
15
16
final class ArrayCollection implements ArrayAccess, IteratorAggregate, Countable
17
{
18
    private array $data;
19
20
    /**
21
     * @var array|null Result array cache
22
     */
23
    private ?array $array = null;
24
25
    /**
26
     * @var ModifierInterface[]
27
     */
28
    private array $modifiers;
29
30
    /**
31
     * @param array $data
32
     * @param ModifierInterface|ModifierInterface[] $modifiers
33
     */
34
    public function __construct(array $data = [], $modifiers = [])
35
    {
36
        $this->data = $data;
37
        $this->modifiers = is_array($modifiers) ? $modifiers : [$modifiers];
38
    }
39
40
    public function withData(array $data): self
41
    {
42
        $new = clone $this;
43
        $new->data = $data;
44
        return $new;
45
    }
46
47
    /**
48
     * @return ModifierInterface[]
49
     */
50
    public function getModifiers(): array
51
    {
52
        return $this->modifiers;
53
    }
54
55
    public function withModifier(ModifierInterface ...$modifiers): self
56
    {
57
        return $this->withModifiers($modifiers);
58
    }
59
60
    /**
61
     * @param ModifierInterface[] $modifiers
62
     * @return self
63
     */
64
    public function withModifiers(array $modifiers): self
65
    {
66
        $new = clone $this;
67
        $new->modifiers = $modifiers;
68
        return $new;
69
    }
70
71
    public function withAddedModifier(ModifierInterface ...$modifiers): self
72
    {
73
        return $this->withAddedModifiers($modifiers);
74
    }
75
76
    /**
77
     * @param ModifierInterface[] $modifiers
78
     * @return self
79
     */
80
    public function withAddedModifiers(array $modifiers): self
81
    {
82
        $new = clone $this;
83
        $new->modifiers = array_merge($new->modifiers, $modifiers);
84
        return $new;
85
    }
86
87
    /**
88
     * @param array|self ...$args
89
     * @return self
90
     */
91
    public function mergeWith(...$args): self
92
    {
93
        array_unshift($args, $this);
94
95
        $arrays = [];
96
        foreach ($args as $arg) {
97
            $arrays[] = $arg instanceof self ? $arg->data : $arg;
98
        }
99
100
        $collections = [];
101
        foreach ($args as $index => $arg) {
102
            $collection = $arg instanceof self ? $arg : new self($arg);
103
            foreach ($collection->getModifiers() as $modifier) {
104
                if ($modifier instanceof BeforeMergeModifierInterface) {
105
                    $collection->data = $modifier->beforeMerge($arrays, $index);
106
                }
107
            }
108
            $collections[$index] = $collection;
109
        }
110
111
        $collection = $this->merge(...$collections);
112
113
        foreach ($collection->getModifiers() as $modifier) {
114
            if ($modifier instanceof AfterMergeModifierInterface) {
115
                $collection->data = $modifier->afterMerge($collection->data);
116
            }
117
        }
118
119
        return $collection;
120
    }
121
122
    /**
123
     * @param array|self ...$args
124
     * @return self
125
     */
126
    private function merge(...$args): self
127
    {
128
        $collection = new ArrayCollection();
129
130
        while (!empty($args)) {
131
            $array = array_shift($args);
132
133
            if ($array instanceof ArrayCollection) {
134
                $collection->modifiers = array_merge($collection->modifiers, $array->modifiers);
135
                $collection->data = $this->merge($collection->data, $array->data)->data;
136
                continue;
137
            }
138
139
            foreach ($array as $k => $v) {
140
                if (is_int($k)) {
141
                    if (array_key_exists($k, $collection->data)) {
142
                        if ($collection->data[$k] !== $v) {
143
                            $collection->data[] = $v;
144
                        }
145
                    } else {
146
                        $collection->data[$k] = $v;
147
                    }
148
                } elseif (
149
                    static::isMergable($v) &&
0 ignored issues
show
Bug Best Practice introduced by
The method Yiisoft\Arrays\Collectio...ollection::isMergable() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

149
                    static::/** @scrutinizer ignore-call */ 
150
                            isMergable($v) &&
Loading history...
150
                    isset($collection->data[$k]) &&
151
                    static::isMergable($collection->data[$k])
152
                ) {
153
                    $mergedCollection = $this->merge($collection->data[$k], $v);
154
                    $collection->data[$k] = ($collection->data[$k] instanceof self || $v instanceof self)
155
                        ? $mergedCollection
156
                        : $mergedCollection->data;
157
                } else {
158
                    $collection->data[$k] = $v;
159
                }
160
            }
161
        }
162
163
        return $collection;
164
    }
165
166
    /**
167
     * @param mixed $value
168
     * @return bool
169
     */
170
    private function isMergable($value): bool
171
    {
172
        return is_array($value) || $value instanceof self;
173
    }
174
175
    public function toArray(): array
176
    {
177
        if ($this->array === null) {
178
            $this->array = $this->performArray($this->data);
179
180
            foreach ($this->modifiers as $modifier) {
181
                if ($modifier instanceof DataModifierInterface) {
182
                    $this->array = $modifier->apply($this->array);
0 ignored issues
show
Bug introduced by
$this->array of type null is incompatible with the type array expected by parameter $data of Yiisoft\Arrays\Collectio...ifierInterface::apply(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

182
                    $this->array = $modifier->apply(/** @scrutinizer ignore-type */ $this->array);
Loading history...
183
                }
184
            }
185
        }
186
        return $this->array;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->array could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
187
    }
188
189
    private function performArray(array $array): array
190
    {
191
        foreach ($array as $k => $v) {
192
            if ($v instanceof ArrayCollection) {
193
                $array[$k] = $v->toArray();
194
            } elseif (is_array($v)) {
195
                $array[$k] = $this->performArray($v);
196
            } else {
197
                $array[$k] = $v;
198
            }
199
        }
200
        return $array;
201
    }
202
203
    /**
204
     * Returns an iterator for traversing the data.
205
     * This method is required by the SPL interface {@see IteratorAggregate}.
206
     * It will be implicitly called when you use `foreach` to traverse the collection.
207
     * @return ArrayIterator an iterator for traversing the cookies in the collection.
208
     */
209
    public function getIterator(): ArrayIterator
210
    {
211
        return new ArrayIterator($this->toArray());
212
    }
213
214
    /**
215
     * Returns the number of data items.
216
     * This method is required by {@see Countable} interface.
217
     * @return int number of data elements.
218
     */
219
    public function count(): int
220
    {
221
        return count($this->toArray());
222
    }
223
224
    /**
225
     * This method is required by the interface {@see ArrayAccess}.
226
     * @param mixed $offset the offset to check on
227
     * @return bool
228
     */
229
    public function offsetExists($offset): bool
230
    {
231
        return isset($this->toArray()[$offset]);
232
    }
233
234
    /**
235
     * This method is required by the interface {@see ArrayAccess}.
236
     * @param mixed $offset the offset to retrieve element.
237
     * @return mixed the element at the offset, null if no element is found at the offset
238
     */
239
    public function offsetGet($offset)
240
    {
241
        return $this->toArray()[$offset] ?? null;
242
    }
243
244
    /**
245
     * @param mixed $offset the offset to set element
246
     * @param mixed $value the element value
247
     */
248
    public function offsetSet($offset, $value): void
249
    {
250
        throw new ArrayCollectionIsImmutableException();
251
    }
252
253
    /**
254
     * @param mixed $offset the offset to unset element
255
     */
256
    public function offsetUnset($offset): void
257
    {
258
        throw new ArrayCollectionIsImmutableException();
259
    }
260
}
261