1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This file is part of the PHPMongo package. |
5
|
|
|
* |
6
|
|
|
* (c) Dmytro Sokil <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Sokil\Mongo; |
13
|
|
|
|
14
|
|
|
class Operator implements ArrayableInterface |
15
|
|
|
{ |
16
|
|
|
/** |
17
|
|
|
* |
18
|
|
|
* @var array list of update operations |
19
|
|
|
*/ |
20
|
|
|
private $operators = array(); |
21
|
|
|
|
22
|
|
|
public function set($fieldName, $value) |
23
|
|
|
{ |
24
|
|
|
if (!isset($this->operators['$set'])) { |
25
|
|
|
$this->operators['$set'] = array(); |
26
|
|
|
} |
27
|
|
|
|
28
|
|
|
$this->operators['$set'][$fieldName] = Structure::prepareToStore($value); |
29
|
|
|
|
30
|
|
|
return $this; |
31
|
|
|
} |
32
|
|
|
|
33
|
|
|
public function push($fieldName, $value) |
34
|
|
|
{ |
35
|
|
|
// prepare to store |
36
|
|
|
$value = Structure::prepareToStore($value); |
37
|
|
|
|
38
|
|
|
// no $push operator found |
39
|
|
|
if (!isset($this->operators['$push'])) { |
40
|
|
|
$this->operators['$push'] = array(); |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
if (!isset($this->operators['$push'][$fieldName])) { |
44
|
|
|
// no field name found |
45
|
|
|
$this->operators['$push'][$fieldName] = $value; |
46
|
|
|
} else { |
47
|
|
|
$oldValue = $this->operators['$push'][$fieldName]; |
48
|
|
|
if (!is_array($oldValue) || !isset($oldValue['$each'])) { |
49
|
|
|
// field name found and has single value |
50
|
|
|
$this->operators['$push'][$fieldName] = array( |
51
|
|
|
'$each' => array($oldValue, $value) |
52
|
|
|
); |
53
|
|
|
} else { |
54
|
|
|
// field name found and already $each |
55
|
|
|
$this->operators['$push'][$fieldName]['$each'][] = $value; |
56
|
|
|
} |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
return $this; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
public function pushEach($fieldName, array $values) |
63
|
|
|
{ |
64
|
|
|
// value must be list, not dictionary |
65
|
|
|
$values = array_values($values); |
66
|
|
|
|
67
|
|
|
// prepare to store |
68
|
|
|
$values = Structure::prepareToStore($values); |
69
|
|
|
|
70
|
|
|
// no $push operator found |
71
|
|
|
if (!isset($this->operators['$push'])) { |
72
|
|
|
$this->operators['$push'] = array(); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
// no field name found |
76
|
|
|
if (!isset($this->operators['$push'][$fieldName])) { |
77
|
|
|
$this->operators['$push'][$fieldName] = array( |
78
|
|
|
'$each' => $values |
79
|
|
|
); |
80
|
|
|
} // field name found and has single value |
81
|
|
|
elseif (!is_array($this->operators['$push'][$fieldName]) || !isset($this->operators['$push'][$fieldName]['$each'])) { |
82
|
|
|
$oldValue = $this->operators['$push'][$fieldName]; |
83
|
|
|
$this->operators['$push'][$fieldName] = array( |
84
|
|
|
'$each' => array_merge(array($oldValue), $values) |
85
|
|
|
); |
86
|
|
|
} // field name found and already $each |
87
|
|
|
else { |
88
|
|
|
$this->operators['$push'][$fieldName]['$each'] = array_merge( |
89
|
|
|
$this->operators['$push'][$fieldName]['$each'], |
90
|
|
|
$values |
91
|
|
|
); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
return $this; |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* The $slice modifier limits the number of array elements during a |
99
|
|
|
* $push operation. To project, or return, a specified number of array |
100
|
|
|
* elements from a read operation, see the $slice projection operator instead. |
101
|
|
|
* |
102
|
|
|
* @link http://docs.mongodb.org/manual/reference/operator/update/slice |
103
|
|
|
* @param string $field |
104
|
|
|
* @param int $slice |
105
|
|
|
* @return \Sokil\Mongo\Operator |
106
|
|
|
* @throws \Sokil\Mongo\Exception |
107
|
|
|
*/ |
108
|
|
|
public function pushEachSlice($field, $slice) |
109
|
|
|
{ |
110
|
|
|
$slice = (int) $slice; |
111
|
|
|
|
112
|
|
|
if (!isset($this->operators['$push'][$field]['$each'])) { |
113
|
|
|
throw new Exception('Field ' . $field . ' must be pushed wit $each modifier'); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
$this->operators['$push'][$field]['$slice'] = $slice; |
117
|
|
|
|
118
|
|
|
return $this; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* The $sort modifier orders the elements of an array during a $push operation. |
123
|
|
|
* |
124
|
|
|
* @link http://docs.mongodb.org/manual/reference/operator/update/sort |
125
|
|
|
* @param string $field |
126
|
|
|
* @param array $sort |
127
|
|
|
* @return \Sokil\Mongo\Operator |
128
|
|
|
* @throws \Sokil\Mongo\Exception |
129
|
|
|
*/ |
130
|
|
View Code Duplication |
public function pushEachSort($field, array $sort) |
|
|
|
|
131
|
|
|
{ |
132
|
|
|
// add modifiers |
133
|
|
|
if (empty($sort)) { |
134
|
|
|
throw new Exception('Sort condition is empty'); |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
if (!isset($this->operators['$push'][$field]['$each'])) { |
138
|
|
|
throw new Exception('Field ' . $field . ' must be pushed with $each modifier'); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
$this->operators['$push'][$field]['$sort'] = $sort; |
142
|
|
|
|
143
|
|
|
return $this; |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* The $position modifier specifies the location in the array at which |
148
|
|
|
* the $push operator insert elements. Without the $position modifier, |
149
|
|
|
* the $push operator inserts elements to the end of the array. See |
150
|
|
|
* $push modifiers for more information. |
151
|
|
|
* |
152
|
|
|
* @link http://docs.mongodb.org/manual/reference/operator/update/position |
153
|
|
|
* @param string $field |
154
|
|
|
* @param int $position non-negative number that corresponds to the position in the array, based on a zero-based index |
155
|
|
|
* @return \Sokil\Mongo\Operator |
156
|
|
|
* @throws \Sokil\Mongo\Exception |
157
|
|
|
*/ |
158
|
|
View Code Duplication |
public function pushEachPosition($field, $position) |
|
|
|
|
159
|
|
|
{ |
160
|
|
|
$position = (int) $position; |
161
|
|
|
|
162
|
|
|
// add modifiers |
163
|
|
|
if ($position <= 0) { |
164
|
|
|
throw new Exception('Position must be greater 0'); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
if (!isset($this->operators['$push'][$field]['$each'])) { |
168
|
|
|
throw new Exception('Field ' . $field . ' must be pushed with $each modifier'); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
$this->operators['$push'][$field]['$position'] = $position; |
172
|
|
|
|
173
|
|
|
return $this; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
public function addToSet($field, $value) |
177
|
|
|
{ |
178
|
|
|
// new field |
179
|
|
|
if (!isset($this->operators['$addToSet'][$field])) { |
180
|
|
|
$this->operators['$addToSet'][$field] = $value; |
181
|
|
|
return $this; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
// scalar value or array in existed field |
185
|
|
View Code Duplication |
if (!is_array($this->operators['$addToSet'][$field]) || !isset($this->operators['$addToSet'][$field]['$each'])) { |
|
|
|
|
186
|
|
|
$this->operators['$addToSet'][$field] = array( |
187
|
|
|
'$each' => array( |
188
|
|
|
$this->operators['$addToSet'][$field], |
189
|
|
|
$value, |
190
|
|
|
), |
191
|
|
|
); |
192
|
|
|
return $this; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
// field already $each |
196
|
|
|
$this->operators['$addToSet'][$field]['$each'][] = $value; |
197
|
|
|
|
198
|
|
|
return $this; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
public function addToSetEach($field, array $values) |
202
|
|
|
{ |
203
|
|
|
// new field |
204
|
|
|
if (!isset($this->operators['$addToSet'][$field])) { |
205
|
|
|
$this->operators['$addToSet'][$field]['$each'] = $values; |
206
|
|
|
return $this; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
// scalar value or array in existed field |
210
|
|
View Code Duplication |
if (!is_array($this->operators['$addToSet'][$field]) || !isset($this->operators['$addToSet'][$field]['$each'])) { |
|
|
|
|
211
|
|
|
$this->operators['$addToSet'][$field] = array( |
212
|
|
|
'$each' => array_merge( |
213
|
|
|
array($this->operators['$addToSet'][$field]), |
214
|
|
|
$values |
215
|
|
|
), |
216
|
|
|
); |
217
|
|
|
return $this; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
// field already $each |
221
|
|
|
$this->operators['$addToSet'][$field] = array( |
222
|
|
|
'$each' => array_merge( |
223
|
|
|
$this->operators['$addToSet'][$field]['$each'], |
224
|
|
|
$values |
225
|
|
|
), |
226
|
|
|
); |
227
|
|
|
|
228
|
|
|
return $this; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
public function increment($fieldName, $value = 1) |
232
|
|
|
{ |
233
|
|
|
// check if update operations already added |
234
|
|
|
$oldIncrementValue = $this->get('$inc', $fieldName); |
235
|
|
|
if ($oldIncrementValue) { |
236
|
|
|
$value = $oldIncrementValue + $value; |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
$this->operators['$inc'][$fieldName] = $value; |
240
|
|
|
|
241
|
|
|
return $this; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* The $pull operator removes from an existing array all instances of a |
246
|
|
|
* value or values that match a specified query. |
247
|
|
|
* |
248
|
|
|
* @link http://docs.mongodb.org/manual/reference/operator/update/pull |
249
|
|
|
* @param integer|string|\Sokil\Mongo\Expression|callable $expression |
250
|
|
|
* @param mixed|\Sokil\Mongo\Expression|callable $value |
251
|
|
|
* @return \Sokil\Mongo\Operator |
252
|
|
|
*/ |
253
|
|
|
public function pull($expression, $value = null) |
254
|
|
|
{ |
255
|
|
|
// field-value pulling |
256
|
|
|
if ($value) { |
257
|
|
|
// expression |
258
|
|
|
if (is_callable($value)) { |
259
|
|
|
$configurator = $value; |
260
|
|
|
$value = new Expression(); |
261
|
|
|
call_user_func($configurator, $value); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
if ($value instanceof Expression) { |
265
|
|
|
$value = $value->toArray(); |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
$this->operators['$pull'][$expression] = $value; |
269
|
|
|
|
270
|
|
|
return $this; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
// expression |
274
|
|
|
if (is_callable($expression)) { |
275
|
|
|
$configurator = $expression; |
276
|
|
|
$expression = new Expression(); |
277
|
|
|
call_user_func($configurator, $expression); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
View Code Duplication |
if ($expression instanceof Expression) { |
|
|
|
|
281
|
|
|
$expression = $expression->toArray(); |
282
|
|
|
} elseif (!is_array($expression)) { |
283
|
|
|
throw new \InvalidArgumentException('Expression must be field name, callable or Expression object'); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
if (!isset($this->operators['$pull'])) { |
287
|
|
|
// no $pull operator found |
288
|
|
|
$this->operators['$pull'] = $expression; |
289
|
|
|
} else { |
290
|
|
|
// $pull operator found |
291
|
|
|
$this->operators['$pull'] = array_merge($this->operators['$pull'], $expression); |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
return $this; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
/** |
298
|
|
|
* The $unset operator deletes a particular field |
299
|
|
|
* |
300
|
|
|
* @link http://docs.mongodb.org/manual/reference/operator/update/unset |
301
|
|
|
* @param string $fieldName |
302
|
|
|
* @return \Sokil\Mongo\Operator |
303
|
|
|
*/ |
304
|
|
|
public function unsetField($fieldName) |
305
|
|
|
{ |
306
|
|
|
$this->operators['$unset'][$fieldName] = ''; |
307
|
|
|
return $this; |
308
|
|
|
} |
309
|
|
|
|
310
|
|
|
public function bitwiceAnd($field, $value) |
311
|
|
|
{ |
312
|
|
|
$this->operators['$bit'][$field]['and'] = (int) $value; |
313
|
|
|
return $this; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
public function bitwiceOr($field, $value) |
317
|
|
|
{ |
318
|
|
|
$this->operators['$bit'][$field]['or'] = (int) $value; |
319
|
|
|
return $this; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
public function bitwiceXor($field, $value) |
323
|
|
|
{ |
324
|
|
|
$this->operators['$bit'][$field]['xor'] = (int) $value; |
325
|
|
|
|
326
|
|
|
return $this; |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
public function isDefined() |
330
|
|
|
{ |
331
|
|
|
return (bool) $this->operators; |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
public function reset() |
335
|
|
|
{ |
336
|
|
|
$this->operators = array(); |
337
|
|
|
return $this; |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
public function get($operation, $fieldName = null) |
341
|
|
|
{ |
342
|
|
|
if ($fieldName) { |
343
|
|
|
return isset($this->operators[$operation][$fieldName]) |
344
|
|
|
? $this->operators[$operation][$fieldName] |
345
|
|
|
: null; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
return isset($this->operators[$operation]) |
349
|
|
|
? $this->operators[$operation] |
350
|
|
|
: null; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* @deprecated since v.1.13 use Operator::toArray() |
355
|
|
|
* @return array |
356
|
|
|
*/ |
357
|
|
|
public function getAll() |
358
|
|
|
{ |
359
|
|
|
return $this->operators; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
public function toArray() |
363
|
|
|
{ |
364
|
|
|
return $this->operators; |
365
|
|
|
} |
366
|
|
|
|
367
|
|
|
public function isReloadRequired() |
368
|
|
|
{ |
369
|
|
|
return isset($this->operators['$inc']) || isset($this->operators['$pull']); |
370
|
|
|
} |
371
|
|
|
|
372
|
|
|
/** |
373
|
|
|
* Transform operator in different formats to canonical array form |
374
|
|
|
* |
375
|
|
|
* @param mixed $mixed |
376
|
|
|
* @return array |
377
|
|
|
* @throws \Sokil\Mongo\Exception |
378
|
|
|
*/ |
379
|
|
|
public static function convertToArray($mixed) |
380
|
|
|
{ |
381
|
|
|
// get operator from callable |
382
|
|
|
if (is_callable($mixed)) { |
383
|
|
|
$callable = $mixed; |
384
|
|
|
$mixed = new self(); |
385
|
|
|
call_user_func($callable, $mixed); |
386
|
|
|
} |
387
|
|
|
|
388
|
|
|
// get operator array |
389
|
|
|
if ($mixed instanceof ArrayableInterface && $mixed instanceof self) { |
390
|
|
|
$mixed = $mixed->toArray(); |
391
|
|
|
} elseif (!is_array($mixed)) { |
392
|
|
|
throw new Exception('Mixed must be instance of Operator'); |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
return $mixed; |
396
|
|
|
} |
397
|
|
|
} |
398
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.