Passed
Pull Request — master (#62)
by Sergei
12:19
created

ArrayCollection::mergeWith()   B

Complexity

Conditions 9
Paths 63

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 27
rs 8.0555
cc 9
nc 63
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Arrays\Collection;
6
7
use ArrayAccess;
8
use Countable;
9
use IteratorAggregate;
10
use Yiisoft\Arrays\ArrayAccessTrait;
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
    use ArrayAccessTrait;
19
20
    private array $data;
21
22
    /**
23
     * @var ModifierInterface[]
24
     */
25
    private array $modifiers = [];
26
27
    public function __construct(array $data = [])
28
    {
29
        $this->data = $data;
30
    }
31
32
    public function withData(array $data): self
33
    {
34
        $new = clone $this;
35
        $new->data = $data;
36
        return $new;
37
    }
38
39
    /**
40
     * @return ModifierInterface[]
41
     */
42
    public function getModifiers(): array
43
    {
44
        return $this->modifiers;
45
    }
46
47
    public function withModifier(ModifierInterface ...$modifiers): self
48
    {
49
        return $this->withModifiers($modifiers);
50
    }
51
52
    /**
53
     * @param ModifierInterface[] $modifiers
54
     * @return self
55
     */
56
    public function withModifiers(array $modifiers): self
57
    {
58
        $new = clone $this;
59
        $new->modifiers = $modifiers;
60
        return $new;
61
    }
62
63
    public function withAddedModifier(ModifierInterface ...$modifiers): self
64
    {
65
        return $this->withAddedModifiers($modifiers);
66
    }
67
68
    /**
69
     * @param ModifierInterface[] $modifiers
70
     * @return self
71
     */
72
    public function withAddedModifiers(array $modifiers): self
73
    {
74
        $new = clone $this;
75
        $new->modifiers = array_merge($new->modifiers, $modifiers);
76
        return $new;
77
    }
78
79
    /**
80
     * @param int|string $key
81
     * @return bool
82
     */
83
    public function keyExists($key): bool
84
    {
85
        return array_key_exists($key, $this->data);
86
    }
87
88
    /**
89
     * @param array|self ...$args
90
     * @return self
91
     */
92
    public function mergeWith(...$args): self
93
    {
94
        $arrays = [];
95
        foreach ($args as $arg) {
96
            $arrays[] = $arg instanceof self ? $arg->data : $arg;
97
        }
98
99
        $collections = [];
100
        foreach ($args as $index => $arg) {
101
            $collection = $arg instanceof self ? $arg : new self($arg);
102
            foreach ($collection->getModifiers() as $modifier) {
103
                if ($modifier instanceof BeforeMergeModifierInterface) {
104
                    $collection->data = $modifier->beforeMerge($arrays, $index);
105
                }
106
            }
107
            $collections[$index] = $collection;
108
        }
109
110
        $collection = $this->merge(...$collections);
111
112
        foreach ($collection->getModifiers() as $modifier) {
113
            if ($modifier instanceof AfterMergeModifierInterface) {
114
                $collection->data = $modifier->afterMerge($collection->data);
115
            }
116
        }
117
118
        return $collection;
119
    }
120
121
    /**
122
     * @param array|self ...$args
123
     * @return self
124
     */
125
    private function merge(...$args): self
126
    {
127
        $collection = new ArrayCollection();
128
129
        while (!empty($args)) {
130
            $array = array_shift($args);
131
132
            if ($array instanceof ArrayCollection) {
133
                $collection->modifiers = array_merge($collection->modifiers, $array->modifiers);
134
                $collection->data = $this->merge($collection->data, $array->data)->data;
135
                continue;
136
            }
137
138
            foreach ($array as $k => $v) {
139
                if (is_int($k)) {
140
                    if ($collection->keyExists($k)) {
141
                        if ($collection[$k] !== $v) {
142
                            $collection->data[] = $v;
143
                        }
144
                    } else {
145
                        $collection->data[$k] = $v;
146
                    }
147
                } elseif (static::isMergable($v) && isset($collection[$k]) && static::isMergable($collection[$k])) {
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

147
                } elseif (static::/** @scrutinizer ignore-call */ isMergable($v) && isset($collection[$k]) && static::isMergable($collection[$k])) {
Loading history...
148
                    $collection->data[$k] = $this->merge($collection[$k], $v)->data;
149
                } else {
150
                    $collection->data[$k] = $v;
151
                }
152
            }
153
        }
154
155
        return $collection;
156
    }
157
158
    /**
159
     * @param mixed $value
160
     * @return bool
161
     */
162
    private function isMergable($value): bool
163
    {
164
        return is_array($value) || $value instanceof self;
165
    }
166
167
    public function toArray(): array
168
    {
169
        $array = $this->performArray($this->getIterator()->getArrayCopy());
170
171
        foreach ($this->modifiers as $modifier) {
172
            if ($modifier instanceof DataModifierInterface) {
173
                $array = $modifier->apply($array);
174
            }
175
        }
176
177
        return $array;
178
    }
179
180
    private function performArray(array $array): array
181
    {
182
        foreach ($array as $k => $v) {
183
            if ($v instanceof ArrayCollection) {
184
                $array[$k] = $v->toArray();
185
            } elseif (is_array($v)) {
186
                $array[$k] = $this->performArray($v);
187
            } else {
188
                $array[$k] = $v;
189
            }
190
        }
191
        return $array;
192
    }
193
194
    /**
195
     * @param mixed $offset the offset to set element
196
     * @param mixed $value the element value
197
     */
198
    public function offsetSet($offset, $value): void
199
    {
200
        throw new ArrayCollectionIsImmutableException();
201
    }
202
203
    /**
204
     * @param mixed $offset the offset to unset element
205
     */
206
    public function offsetUnset($offset): void
207
    {
208
        throw new ArrayCollectionIsImmutableException();
209
    }
210
}
211