Passed
Pull Request — master (#62)
by Sergei
10:53
created

ArrayCollection::count()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
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
    public function __construct(array $data = [], ModifierInterface ...$modifiers)
31
    {
32
        $this->data = $data;
33
        $this->modifiers = $modifiers;
34
    }
35
36
    public function withData(array $data): self
37
    {
38
        $new = clone $this;
39
        $new->data = $data;
40
        return $new;
41
    }
42
43
    /**
44
     * @return ModifierInterface[]
45
     */
46
    public function getModifiers(): array
47
    {
48
        return $this->modifiers;
49
    }
50
51
    public function withModifiers(ModifierInterface ...$modifiers): self
52
    {
53
        $new = clone $this;
54
        $new->modifiers = $modifiers;
55
        return $new;
56
    }
57
58
    public function withAddedModifiers(ModifierInterface ...$modifiers): self
59
    {
60
        $new = clone $this;
61
        $new->modifiers = array_merge($new->modifiers, $modifiers);
62
        return $new;
63
    }
64
65
    /**
66
     * @param array|self ...$args
67
     * @return self
68
     */
69
    public function mergeWith(...$args): self
70
    {
71
        array_unshift($args, $this);
72
73
        $arrays = [];
74
        foreach ($args as $arg) {
75
            $arrays[] = $arg instanceof self ? $arg->data : $arg;
76
        }
77
78
        $collections = [];
79
        foreach ($args as $index => $arg) {
80
            $collection = $arg instanceof self ? $arg : new self($arg);
81
            foreach ($collection->getModifiers() as $modifier) {
82
                if ($modifier instanceof BeforeMergeModifierInterface) {
83
                    $collection->data = $modifier->beforeMerge($arrays, $index);
84
                }
85
            }
86
            $collections[$index] = $collection;
87
        }
88
89
        $collection = $this->merge(...$collections);
90
91
        foreach ($collection->getModifiers() as $modifier) {
92
            if ($modifier instanceof AfterMergeModifierInterface) {
93
                $collection->data = $modifier->afterMerge($collection->data);
94
            }
95
        }
96
97
        return $collection;
98
    }
99
100
    /**
101
     * @param array|self ...$args
102
     * @return self
103
     */
104
    private function merge(...$args): self
105
    {
106
        $collection = new ArrayCollection();
107
108
        while (!empty($args)) {
109
            $array = array_shift($args);
110
111
            if ($array instanceof ArrayCollection) {
112
                $collection->modifiers = array_merge($collection->modifiers, $array->modifiers);
113
                $collection->data = $this->merge($collection->data, $array->data)->data;
114
                continue;
115
            }
116
117
            foreach ($array as $k => $v) {
118
                if (is_int($k)) {
119
                    if (array_key_exists($k, $collection->data)) {
120
                        if ($collection->data[$k] !== $v) {
121
                            $collection->data[] = $v;
122
                        }
123
                    } else {
124
                        $collection->data[$k] = $v;
125
                    }
126
                } elseif (
127
                    isset($collection->data[$k]) &&
128
                    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
                    static::isMergeable($collection->data[$k])
130
                ) {
131
                    $mergedCollection = $this->merge($collection->data[$k], $v);
132
                    $collection->data[$k] = ($collection->data[$k] instanceof self || $v instanceof self)
133
                        ? $mergedCollection
134
                        : $mergedCollection->data;
135
                } else {
136
                    $collection->data[$k] = $v;
137
                }
138
            }
139
        }
140
141
        return $collection;
142
    }
143
144
    /**
145
     * @param mixed $value
146
     * @return bool
147
     */
148
    private function isMergeable($value): bool
149
    {
150
        return is_array($value) || $value instanceof self;
151
    }
152
153
    public function toArray(): array
154
    {
155
        if ($this->array === null) {
156
            $this->array = $this->performArray($this->data);
157
158
            foreach ($this->modifiers as $modifier) {
159
                if ($modifier instanceof DataModifierInterface) {
160
                    $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
        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
    private function performArray(array $array): array
168
    {
169
        foreach ($array as $k => $v) {
170
            if ($v instanceof ArrayCollection) {
171
                $array[$k] = $v->toArray();
172
            } elseif (is_array($v)) {
173
                $array[$k] = $this->performArray($v);
174
            } else {
175
                $array[$k] = $v;
176
            }
177
        }
178
        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
    public function getIterator(): ArrayIterator
188
    {
189
        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
    public function count(): int
198
    {
199
        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
    public function offsetExists($offset): bool
208
    {
209
        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
    public function offsetGet($offset)
218
    {
219
        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
    public function offsetSet($offset, $value): void
227
    {
228
        throw new ArrayCollectionIsImmutableException();
229
    }
230
231
    /**
232
     * @param mixed $offset the offset to unset element
233
     */
234
    public function offsetUnset($offset): void
235
    {
236
        throw new ArrayCollectionIsImmutableException();
237
    }
238
239
    public function __clone()
240
    {
241
        $this->array = null;
242
    }
243
}
244