Passed
Pull Request — master (#62)
by Sergei
06:40 queued 04:40
created

ArrayCollection::offsetSet()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 34
    public function __construct(array $data = [], ModifierInterface ...$modifiers)
31
    {
32 34
        $this->data = $data;
33 34
        $this->modifiers = $modifiers;
34 34
    }
35
36 2
    public function withData(array $data): self
37
    {
38 2
        $new = clone $this;
39 2
        $new->data = $data;
40 2
        return $new;
41
    }
42
43
    /**
44
     * @return ModifierInterface[]
45
     */
46 25
    public function getModifiers(): array
47
    {
48 25
        return $this->modifiers;
49
    }
50
51 8
    public function withModifiers(ModifierInterface ...$modifiers): self
52
    {
53 8
        $new = clone $this;
54 8
        $new->modifiers = $modifiers;
55 8
        return $new;
56
    }
57
58 1
    public function withAddedModifiers(ModifierInterface ...$modifiers): self
59
    {
60 1
        $new = clone $this;
61 1
        $new->modifiers = array_merge($new->modifiers, $modifiers);
62 1
        return $new;
63
    }
64
65
    /**
66
     * @param array|self ...$args
67
     * @return self
68
     */
69 23
    public function mergeWith(...$args): self
70
    {
71 23
        array_unshift($args, $this);
72
73 23
        $arrays = [];
74 23
        foreach ($args as $arg) {
75 23
            $arrays[] = $arg instanceof self ? $arg->data : $arg;
76
        }
77
78 23
        $collections = [];
79 23
        foreach ($args as $index => $arg) {
80 23
            $collection = $arg instanceof self ? $arg : new self($arg);
81 23
            foreach ($collection->getModifiers() as $modifier) {
82 16
                if ($modifier instanceof BeforeMergeModifierInterface) {
83 12
                    $collection->data = $modifier->beforeMerge($arrays, $index);
84
                }
85
            }
86 23
            $collections[$index] = $collection;
87
        }
88
89 23
        $collection = $this->merge(...$collections);
90
91 23
        foreach ($collection->getModifiers() as $modifier) {
92 16
            if ($modifier instanceof AfterMergeModifierInterface) {
93 12
                $collection->data = $modifier->afterMerge($collection->data);
94
            }
95
        }
96
97 23
        return $collection;
98
    }
99
100
    /**
101
     * @param array|self ...$args
102
     * @return self
103
     */
104 23
    private function merge(...$args): self
105
    {
106 23
        $collection = new ArrayCollection();
107
108 23
        while (!empty($args)) {
109 23
            $array = array_shift($args);
110
111 23
            if ($array instanceof ArrayCollection) {
112 23
                $collection->modifiers = array_merge($collection->modifiers, $array->modifiers);
113 23
                $collection->data = $this->merge($collection->data, $array->data)->data;
114 23
                continue;
115
            }
116
117 23
            foreach ($array as $k => $v) {
118 21
                if (is_int($k)) {
119 10
                    if (array_key_exists($k, $collection->data)) {
120 10
                        if ($collection->data[$k] !== $v) {
121 10
                            $collection->data[] = $v;
122
                        }
123
                    } else {
124 10
                        $collection->data[$k] = $v;
125
                    }
126
                } elseif (
127 18
                    isset($collection->data[$k]) &&
128 18
                    static::isMergeable($v) &&
0 ignored issues
show
Bug Best Practice introduced by
The method Yiisoft\Arrays\Collectio...llection::isMergeable() 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

128
                    static::/** @scrutinizer ignore-call */ 
129
                            isMergeable($v) &&
Loading history...
129 18
                    static::isMergeable($collection->data[$k])
130
                ) {
131 11
                    $mergedCollection = $this->merge($collection->data[$k], $v);
132 11
                    $collection->data[$k] = ($collection->data[$k] instanceof self || $v instanceof self)
133 1
                        ? $mergedCollection
134 11
                        : $mergedCollection->data;
135
                } else {
136 18
                    $collection->data[$k] = $v;
137
                }
138
            }
139
        }
140
141 23
        return $collection;
142
    }
143
144
    /**
145
     * @param mixed $value
146
     * @return bool
147
     */
148 17
    private function isMergeable($value): bool
149
    {
150 17
        return is_array($value) || $value instanceof self;
151
    }
152
153 30
    public function toArray(): array
154
    {
155 30
        if ($this->array === null) {
156 30
            $this->array = $this->performArray($this->data);
157
158 30
            foreach ($this->modifiers as $modifier) {
159 22
                if ($modifier instanceof DataModifierInterface) {
160 10
                    $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

160
                    $this->array = $modifier->apply(/** @scrutinizer ignore-type */ $this->array);
Loading history...
161
                }
162
            }
163
        }
164 30
        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...
165
    }
166
167 30
    private function performArray(array $array): array
168
    {
169 30
        foreach ($array as $k => $v) {
170 28
            if ($v instanceof ArrayCollection) {
171 2
                $array[$k] = $v->toArray();
172 28
            } elseif (is_array($v)) {
173 17
                $array[$k] = $this->performArray($v);
174
            } else {
175 28
                $array[$k] = $v;
176
            }
177
        }
178 30
        return $array;
179
    }
180
181
    /**
182
     * Returns an iterator for traversing the data.
183
     * This method is required by the SPL interface {@see IteratorAggregate}.
184
     * It will be implicitly called when you use `foreach` to traverse the collection.
185
     * @return ArrayIterator an iterator for traversing the cookies in the collection.
186
     */
187 1
    public function getIterator(): ArrayIterator
188
    {
189 1
        return new ArrayIterator($this->toArray());
190
    }
191
192
    /**
193
     * Returns the number of data items.
194
     * This method is required by {@see Countable} interface.
195
     * @return int number of data elements.
196
     */
197 1
    public function count(): int
198
    {
199 1
        return count($this->toArray());
200
    }
201
202
    /**
203
     * This method is required by the interface {@see ArrayAccess}.
204
     * @param mixed $offset the offset to check on
205
     * @return bool
206
     */
207 1
    public function offsetExists($offset): bool
208
    {
209 1
        return isset($this->toArray()[$offset]);
210
    }
211
212
    /**
213
     * This method is required by the interface {@see ArrayAccess}.
214
     * @param mixed $offset the offset to retrieve element.
215
     * @return mixed the element at the offset, null if no element is found at the offset
216
     */
217 1
    public function offsetGet($offset)
218
    {
219 1
        return $this->toArray()[$offset] ?? null;
220
    }
221
222
    /**
223
     * @param mixed $offset the offset to set element
224
     * @param mixed $value the element value
225
     */
226 1
    public function offsetSet($offset, $value): void
227
    {
228 1
        throw new ArrayCollectionIsImmutableException();
229
    }
230
231
    /**
232
     * @param mixed $offset the offset to unset element
233
     */
234 1
    public function offsetUnset($offset): void
235
    {
236 1
        throw new ArrayCollectionIsImmutableException();
237
    }
238
239 10
    public function __clone()
240
    {
241 10
        $this->array = null;
242 10
    }
243
}
244