AbstractArray::filterValue()
last analyzed

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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