Completed
Branch feature/pre-split (656bce)
by Anton
04:24
created

AbstractArray::stateValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
rs 9.4285
1
<?php
2
/**
3
 * Spiral Framework, Core Components
4
 *
5
 * @author    Wolfy-J
6
 */
7
namespace Spiral\ODM\Accessors;
8
9
use Spiral\Models\Traits\SolidableTrait;
10
use Spiral\ODM\CompositableInterface;
11
12
/**
13
 * Provides ability to perform scalar operations on arrays.
14
 *
15
 * Attention, array will be saved as one big $set operation in case when multiple atomic
16
 * operations applied to it (not supported by Mongo).
17
 */
18
abstract class AbstractArray implements CompositableInterface, \Countable, \IteratorAggregate
19
{
20
    use SolidableTrait;
21
22
    /**
23
     * @var array
24
     */
25
    protected $values = [];
26
27
    /**
28
     * Low level atomic operations.
29
     *
30
     * @var array
31
     */
32
    protected $atomics = [];
33
34
    /**
35
     * @param mixed $values
36
     */
37
    public function __construct($values)
38
    {
39
        if (!is_array($values)) {
40
            //Since we have to overwrite non array field
41
            $this->solidState = true;
42
        }
43
44
        $this->addValues($values);
45
    }
46
47
    /**
48
     * Check if value presented in array.
49
     *
50
     * @param mixed $needle
51
     * @param bool  $strict
52
     *
53
     * @return bool
54
     */
55
    public function has($needle, bool $strict = true): bool
56
    {
57
        foreach ($this->values as $value) {
58
            if ($strict && $value === $needle) {
59
                return true;
60
            }
61
62
            if ($strict && $value == $needle) {
63
                return true;
64
            }
65
        }
66
67
        return false;
68
    }
69
70
    /**
71
     * Alias for atomic operation $push. Only values passed type filter will be added.
72
     *
73
     * @param mixed $value
74
     *
75
     * @return self|$this
76
     */
77
    public function push($value): AbstractArray
78
    {
79
        $value = $this->filterValue($value);
80
        if (is_null($value)) {
81
            return $this;
82
        }
83
84
        array_push($this->values, $value);
85
        $this->atomics['$push']['$each'][] = $value;
86
87
        return $this;
88
    }
89
90
    /**
91
     * Alias for atomic operation $addToSet. Only values passed type filter will be added.
92
     *
93
     * @param mixed $value
94
     *
95
     * @return self|$this
96
     */
97
    public function add($value): AbstractArray
98
    {
99
        $value = $this->filterValue($value);
100
        if (is_null($value)) {
101
            return $this;
102
        }
103
104
        if (!in_array($value, $this->values)) {
105
            array_push($this->values, $value);
106
        }
107
108
        $this->atomics['$addToSet']['$each'][] = $value;
109
110
        return $this;
111
    }
112
113
    /**
114
     * Alias for atomic operation $pull. Only values passed type filter will be added.
115
     *
116
     * @param mixed $value
117
     *
118
     * @return self|$this
119
     */
120
    public function pull($value): AbstractArray
121
    {
122
        $value = $this->filterValue($value);
123
        if (is_null($value)) {
124
            return $this;
125
        }
126
127
        //Removing values from array (non strict)
128
        $this->values = array_filter($this->values, function ($item) use ($value) {
129
            return $item != $value;
130
        });
131
132
        $this->atomics['$pull']['$in'][] = $value;
133
134
        return $this;
135
    }
136
137
    /**
138
     * {@inheritdoc}
139
     */
140
    public function stateValue($data)
141
    {
142
        //Manually altered arrays must always end in solid state
143
        $this->solidState = true;
144
145
        //Flushing existed values
146
        $this->values = [];
147
148
        //Pushing filtered values in array
149
        $this->addValues($data);
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155
    public function hasChanges(): bool
156
    {
157
        return !empty($this->atomics);
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163
    public function flushChanges()
164
    {
165
        $this->atomics = [];
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171
    public function buildAtomics(string $container = ''): array
172
    {
173
        if (!$this->hasChanges()) {
174
            return [];
175
        }
176
177
        //Mongo does not support multiple operations for one field, switching to $set (make sure it's
178
        //reasonable)
179
        if ($this->solidState || count($this->atomics) > 1) {
180
            //We don't care about atomics in solid state
181
            return ['$set' => [$container => $this->packValue()]];
182
        }
183
184
        $atomics = [];
185
        foreach ($this->atomics as $operation => $values) {
186
            $atomics[$operation] = [$container => $values];
187
        }
188
189
        return $atomics;
190
    }
191
192
    /**
193
     * @return array
194
     */
195
    public function packValue(): array
196
    {
197
        return array_values($this->values);
198
    }
199
200
    /**
201
     * @return int
202
     */
203
    public function count(): int
204
    {
205
        return count($this->values);
206
    }
207
208
    /**
209
     * @return \ArrayIterator
210
     */
211
    public function getIterator()
212
    {
213
        return new \ArrayIterator($this->values);
214
    }
215
216
    /**
217
     * Clone accessor and ensure that it state is updated.
218
     */
219
    public function __clone()
220
    {
221
        $this->solidState = true;
222
        $this->atomics = [];
223
    }
224
225
    /**
226
     * @return array
227
     */
228
    public function __debugInfo()
229
    {
230
        return [
231
            'values'  => $this->packValue(),
232
            'atomics' => $this->buildAtomics('@scalarArray'),
233
        ];
234
    }
235
236
    /**
237
     * @return array
238
     */
239
    public function jsonSerialize()
240
    {
241
        return $this->packValue();
242
    }
243
244
    /**
245
     * Add values matched with filter.
246
     *
247
     * @param mixed $values
248
     */
249
    protected function addValues($values)
250
    {
251
        if (!is_array($values) && !$values instanceof \Traversable) {
252
            //Unable to process values
253
            return;
254
        }
255
256
        foreach ($values as $value) {
257
            //Passing every value thought the filter
258
            $value = $this->filterValue($value);
259
            if (!is_null($value)) {
260
                $this->values[] = $value;
261
            }
262
        }
263
    }
264
265
    /**
266
     * Filter value, MUST return null if value is invalid.
267
     *
268
     * @param mixed $value
269
     *
270
     * @return mixed|null
271
     */
272
    abstract protected function filterValue($value);
273
}