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
|
|
|
use Sokil\Mongo\Document\RelationManager; |
15
|
|
|
use Sokil\Mongo\Document\RevisionManager; |
16
|
|
|
use Sokil\Mongo\Document\InvalidDocumentException; |
17
|
|
|
use Sokil\Mongo\Collection\Definition; |
18
|
|
|
use Sokil\Mongo\Document\OptimisticLockFailureException; |
19
|
|
|
use Symfony\Component\EventDispatcher\EventDispatcher; |
20
|
|
|
use GeoJson\Geometry\Geometry; |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Instance of this class is a representation of one document from collection. |
24
|
|
|
* |
25
|
|
|
* @link https://github.com/sokil/php-mongo#document-schema Document schema |
26
|
|
|
* @link https://github.com/sokil/php-mongo#create-new-document Create new document |
27
|
|
|
* @link https://github.com/sokil/php-mongo#get-and-set-data-in-document get and set data |
28
|
|
|
* @link https://github.com/sokil/php-mongo#storing-document Saving document |
29
|
|
|
* @link https://github.com/sokil/php-mongo#document-validation Validation |
30
|
|
|
* @link https://github.com/sokil/php-mongo#deleting-collections-and-documents Deleting documents |
31
|
|
|
* @link https://github.com/sokil/php-mongo#events Event handlers |
32
|
|
|
* @link https://github.com/sokil/php-mongo#behaviors Behaviors |
33
|
|
|
* @link https://github.com/sokil/php-mongo#relations Relations |
34
|
|
|
* |
35
|
|
|
* @method \Sokil\Mongo\Document onAfterConstruct(callable $handler, int $priority = 0) |
36
|
|
|
* @method \Sokil\Mongo\Document onBeforeValidate(callable $handler, int $priority = 0) |
37
|
|
|
* @method \Sokil\Mongo\Document onAfterValidate(callable $handler, int $priority = 0) |
38
|
|
|
* @method \Sokil\Mongo\Document onValidateError(callable $handler, int $priority = 0) |
39
|
|
|
* @method \Sokil\Mongo\Document onBeforeInsert(callable $handler, int $priority = 0) |
40
|
|
|
* @method \Sokil\Mongo\Document onAfterInsert(callable $handler, int $priority = 0) |
41
|
|
|
* @method \Sokil\Mongo\Document onBeforeUpdate(callable $handler, int $priority = 0) |
42
|
|
|
* @method \Sokil\Mongo\Document onAfterUpdate(callable $handler, int $priority = 0) |
43
|
|
|
* @method \Sokil\Mongo\Document onBeforeSave(callable $handler, int $priority = 0) |
44
|
|
|
* @method \Sokil\Mongo\Document onAfterSave(callable $handler, int $priority = 0) |
45
|
|
|
* @method \Sokil\Mongo\Document onBeforeDelete(callable $handler, int $priority = 0) |
46
|
|
|
* @method \Sokil\Mongo\Document onAfterDelete(callable $handler, int $priority = 0) |
47
|
|
|
* |
48
|
|
|
* @author Dmytro Sokil <[email protected]> |
49
|
|
|
*/ |
50
|
|
|
class Document extends Structure |
51
|
|
|
{ |
52
|
|
|
const FIELD_TYPE_DOUBLE = 1; |
53
|
|
|
const FIELD_TYPE_STRING = 2; |
54
|
|
|
const FIELD_TYPE_OBJECT = 3; |
55
|
|
|
const FIELD_TYPE_ARRAY = 4; |
56
|
|
|
const FIELD_TYPE_BINARY_DATA = 5; |
57
|
|
|
const FIELD_TYPE_UNDEFINED = 6; // deprecated |
58
|
|
|
const FIELD_TYPE_OBJECT_ID = 7; |
59
|
|
|
const FIELD_TYPE_BOOLEAN = 8; |
60
|
|
|
const FIELD_TYPE_DATE = 9; |
61
|
|
|
const FIELD_TYPE_NULL = 10; |
62
|
|
|
const FIELD_TYPE_REGULAR_EXPRESSION = 11; |
63
|
|
|
const FIELD_TYPE_JAVASCRIPT = 13; |
64
|
|
|
const FIELD_TYPE_SYMBOL = 14; |
65
|
|
|
const FIELD_TYPE_JAVASCRIPT_WITH_SCOPE = 15; |
66
|
|
|
const FIELD_TYPE_INT32 = 16; |
67
|
|
|
const FIELD_TYPE_TIMESTAMP = 17; |
68
|
|
|
const FIELD_TYPE_INT64 = 18; |
69
|
|
|
const FIELD_TYPE_MIN_KEY = 255; |
70
|
|
|
const FIELD_TYPE_MAX_KEY = 127; |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* |
74
|
|
|
* @var \Sokil\Mongo\Document\RelationManager |
75
|
|
|
*/ |
76
|
|
|
private $relationManager; |
77
|
|
|
|
78
|
|
|
const RELATION_HAS_ONE = 'HAS_ONE'; |
79
|
|
|
const RELATION_BELONGS = 'BELONGS'; |
80
|
|
|
const RELATION_HAS_MANY = 'HAS_MANY'; |
81
|
|
|
const RELATION_MANY_MANY = 'MANY_MANY'; |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* |
85
|
|
|
* @var \Sokil\Mongo\Document\RevisionManager |
86
|
|
|
*/ |
87
|
|
|
private $revisionManager; |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* |
91
|
|
|
* @var \Sokil\Mongo\Collection |
92
|
|
|
*/ |
93
|
|
|
private $collection; |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* Name of scenario, used for validating fields |
97
|
|
|
* @var string |
98
|
|
|
*/ |
99
|
|
|
private $scenario; |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* @var array validator errors |
103
|
|
|
*/ |
104
|
|
|
private $errors = array(); |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* @var array manually added validator errors |
108
|
|
|
*/ |
109
|
|
|
private $triggeredErrors = array(); |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* @var \Symfony\Component\EventDispatcher\EventDispatcher Event Dispatcher instance |
113
|
|
|
*/ |
114
|
|
|
private $eventDispatcher; |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* @var \Sokil\Mongo\Operator Modification operator instance |
118
|
|
|
*/ |
119
|
|
|
private $operator; |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* |
123
|
|
|
* @var array list of defined behaviors |
124
|
|
|
*/ |
125
|
|
|
private $behaviors = array(); |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* |
129
|
|
|
* @var array list of namespaces |
130
|
|
|
*/ |
131
|
|
|
private $validatorNamespaces = array( |
132
|
|
|
'\Sokil\Mongo\Validator', |
133
|
|
|
); |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* |
137
|
|
|
* @var array document options |
138
|
|
|
*/ |
139
|
|
|
private $options; |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* @param \Sokil\Mongo\Collection $collection instance of Mongo collection |
143
|
|
|
* @param array $data mongo document |
144
|
|
|
* @param array $options options of object initialization |
145
|
|
|
*/ |
146
|
|
|
public function __construct(Collection $collection, array $data = null, array $options = array()) |
147
|
|
|
{ |
148
|
|
|
$this->collection = $collection; |
149
|
|
|
|
150
|
|
|
// configure document with options |
151
|
|
|
$this->options = $options; |
152
|
|
|
|
153
|
|
|
// init document |
154
|
|
|
$this->initDocument(); |
155
|
|
|
|
156
|
|
|
// execute before construct callable |
157
|
|
|
$this->beforeConstruct(); |
158
|
|
|
|
159
|
|
|
// set data |
160
|
|
|
if ($data) { |
161
|
|
|
if ($this->getOption('stored')) { |
162
|
|
|
// load stored |
163
|
|
|
$this->replace($data); |
164
|
|
|
} else { |
165
|
|
|
// create unstored |
166
|
|
|
$this->merge($data); |
167
|
|
|
} |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
// use versioning |
171
|
|
|
if($this->getOption('versioning')) { |
172
|
|
|
$this->getRevisionManager()->listen(); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
// execure after construct event handlers |
176
|
|
|
$this->eventDispatcher->dispatch('afterConstruct'); |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
public function getOptions() |
180
|
|
|
{ |
181
|
|
|
return $this->options; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
public function getOption($name, $default = null) |
185
|
|
|
{ |
186
|
|
|
return isset($this->options[$name]) ? $this->options[$name] : $default; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
public function hasOption($name) |
190
|
|
|
{ |
191
|
|
|
return isset($this->options[$name]); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Add own namespace of validators |
196
|
|
|
* |
197
|
|
|
* @param type $namespace |
198
|
|
|
* @return \Sokil\Mongo\Document |
199
|
|
|
*/ |
200
|
|
|
public function addValidatorNamespace($namespace) |
201
|
|
|
{ |
202
|
|
|
$this->validatorNamespaces[] = rtrim($namespace, '\\'); |
203
|
|
|
return $this; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Event handler, called before running constructor. |
208
|
|
|
* May be overridden in child classes |
209
|
|
|
*/ |
210
|
|
|
public function beforeConstruct() |
211
|
|
|
{ |
212
|
|
|
|
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Get instance of collection |
217
|
|
|
* @return \Sokil\Mongo\Collection |
218
|
|
|
*/ |
219
|
|
|
public function getCollection() |
220
|
|
|
{ |
221
|
|
|
return $this->collection; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
/** |
225
|
|
|
* Reset all data passed to object in run-time, like events, behaviors, |
226
|
|
|
* data modifications, etc. to the state just after open or save document |
227
|
|
|
* |
228
|
|
|
* @return \Sokil\Mongo\Document |
229
|
|
|
*/ |
230
|
|
|
public function reset() |
231
|
|
|
{ |
232
|
|
|
// reset structure |
233
|
|
|
parent::reset(); |
234
|
|
|
|
235
|
|
|
// reset errors |
236
|
|
|
$this->errors = array(); |
237
|
|
|
$this->triggeredErrors = array(); |
238
|
|
|
|
239
|
|
|
// reset behaviors |
240
|
|
|
$this->clearBehaviors(); |
241
|
|
|
|
242
|
|
|
// init delegates |
243
|
|
|
$this->initDocument(); |
244
|
|
|
|
245
|
|
|
return $this; |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* Reload data from db and reset all unsaved data |
250
|
|
|
*/ |
251
|
|
|
public function refresh() |
252
|
|
|
{ |
253
|
|
|
$data = $this->collection |
254
|
|
|
->getMongoCollection() |
255
|
|
|
->findOne(array( |
256
|
|
|
'_id' => $this->getId() |
257
|
|
|
)); |
258
|
|
|
|
259
|
|
|
$this->replace($data); |
260
|
|
|
|
261
|
|
|
$this->operator->reset(); |
262
|
|
|
|
263
|
|
|
return $this; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
/** |
267
|
|
|
* Initialise relative classes |
268
|
|
|
*/ |
269
|
|
|
private function initDocument() |
270
|
|
|
{ |
271
|
|
|
// start event dispatching |
272
|
|
|
$this->eventDispatcher = new EventDispatcher; |
273
|
|
|
|
274
|
|
|
// create operator |
275
|
|
|
$this->operator = $this->getCollection()->operator(); |
276
|
|
|
|
277
|
|
|
// attach behaviors |
278
|
|
|
$this->attachBehaviors($this->behaviors()); |
279
|
|
|
if($this->hasOption('behaviors')) { |
280
|
|
|
$this->attachBehaviors($this->getOption('behaviors')); |
281
|
|
|
} |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
public function __toString() |
285
|
|
|
{ |
286
|
|
|
return (string) $this->getId(); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
public function __call($name, $arguments) |
290
|
|
|
{ |
291
|
|
|
// behaviors |
292
|
|
|
foreach ($this->behaviors as $behavior) { |
293
|
|
|
if (!method_exists($behavior, $name)) { |
294
|
|
|
continue; |
295
|
|
|
} |
296
|
|
|
|
297
|
|
|
return call_user_func_array(array($behavior, $name), $arguments); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
// adding event |
301
|
|
|
if('on' === substr($name, 0, 2)) { |
302
|
|
|
// prepent ebent name to function args |
303
|
|
|
$addListenerArguments = $arguments; |
304
|
|
|
array_unshift($addListenerArguments, lcfirst(substr($name, 2))); |
305
|
|
|
// add listener |
306
|
|
|
call_user_func_array( |
307
|
|
|
array($this->eventDispatcher, 'addListener'), |
308
|
|
|
$addListenerArguments |
309
|
|
|
); |
310
|
|
|
|
311
|
|
|
return $this; |
312
|
|
|
} |
313
|
|
|
|
314
|
|
|
// getter |
315
|
|
|
if ('get' === strtolower(substr($name, 0, 3))) { |
316
|
|
|
return $this->get(lcfirst(substr($name, 3))); |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
// setter |
320
|
|
|
if ('set' === strtolower(substr($name, 0, 3)) && isset($arguments[0])) { |
321
|
|
|
return $this->set(lcfirst(substr($name, 3)), $arguments[0]); |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
throw new Exception('Document has no method "' . $name . '"'); |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
public function __get($name) |
328
|
|
|
{ |
329
|
|
|
if ($this->getRelationManager()->isRelationExists($name)) { |
330
|
|
|
// resolve relation |
331
|
|
|
return $this->getRelationManager()->getRelated($name); |
332
|
|
|
} else { |
333
|
|
|
// get document parameter |
334
|
|
|
return parent::__get($name); |
335
|
|
|
} |
336
|
|
|
} |
337
|
|
|
|
338
|
|
|
/** |
339
|
|
|
* Set geo data as GeoJson object |
340
|
|
|
* |
341
|
|
|
* Requires MongoDB version 2.4 or above with 2dsparse index version 1 |
342
|
|
|
* to use Point, LineString and Polygon. |
343
|
|
|
* |
344
|
|
|
* Requires MongoDB version 2.6 or above with 2dsparse index version 2 |
345
|
|
|
* to use MultiPoint, MultiLineString, MultiPolygon and GeometryCollection. |
346
|
|
|
* |
347
|
|
|
* @link http://geojson.org/ |
348
|
|
|
* @param string $field |
349
|
|
|
* @param \GeoJson\Geometry\Geometry $geometry |
350
|
|
|
* @return \Sokil\Mongo\Document |
351
|
|
|
*/ |
352
|
|
|
public function setGeometry($field, Geometry $geometry) |
353
|
|
|
{ |
354
|
|
|
return $this->set($field, $geometry); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
/** |
358
|
|
|
* Set point as longitude and latitude |
359
|
|
|
* |
360
|
|
|
* Requires MongoDB version 2.4 or above with 2dsparse index version 1 |
361
|
|
|
* to use Point, LineString and Polygon. |
362
|
|
|
* |
363
|
|
|
* @link http://docs.mongodb.org/manual/core/2dsphere/#point |
364
|
|
|
* @param string $field |
365
|
|
|
* @param float $longitude |
366
|
|
|
* @param float $latitude |
367
|
|
|
* @return \Sokil\Mongo\Document |
368
|
|
|
*/ |
369
|
|
|
public function setPoint($field, $longitude, $latitude) |
370
|
|
|
{ |
371
|
|
|
return $this->setGeometry( |
372
|
|
|
$field, |
373
|
|
|
new \GeoJson\Geometry\Point(array( |
374
|
|
|
$longitude, |
375
|
|
|
$latitude |
376
|
|
|
)) |
377
|
|
|
); |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
/** |
381
|
|
|
* Set point as longitude and latitude in legacy format |
382
|
|
|
* |
383
|
|
|
* May be used 2d index |
384
|
|
|
* |
385
|
|
|
* @link http://docs.mongodb.org/manual/core/2d/#geospatial-indexes-store-grid-coordinates |
386
|
|
|
* @param string $field |
387
|
|
|
* @param float $longitude |
388
|
|
|
* @param float $latitude |
389
|
|
|
* @return \Sokil\Mongo\Document |
390
|
|
|
*/ |
391
|
|
|
public function setLegacyPoint($field, $longitude, $latitude) |
392
|
|
|
{ |
393
|
|
|
return $this->set( |
394
|
|
|
$field, |
395
|
|
|
array($longitude, $latitude) |
396
|
|
|
); |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
/** |
400
|
|
|
* Set line string as array of points |
401
|
|
|
* |
402
|
|
|
* Requires MongoDB version 2.4 or above with 2dsparse index version 1 |
403
|
|
|
* to use Point, LineString and Polygon. |
404
|
|
|
* |
405
|
|
|
* @link http://docs.mongodb.org/manual/core/2dsphere/#linestring |
406
|
|
|
* @param string $field |
407
|
|
|
* @param array $pointArray array of points |
408
|
|
|
* @return \Sokil\Mongo\Document |
409
|
|
|
*/ |
410
|
|
|
public function setLineString($field, array $pointArray) |
411
|
|
|
{ |
412
|
|
|
return $this->setGeometry( |
413
|
|
|
$field, |
414
|
|
|
new \GeoJson\Geometry\LineString($pointArray) |
415
|
|
|
); |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
/** |
419
|
|
|
* Set polygon as array of line rings. |
420
|
|
|
* |
421
|
|
|
* Line ring is closed line string (first and last point same). |
422
|
|
|
* Line string is array of points. |
423
|
|
|
* |
424
|
|
|
* Requires MongoDB version 2.4 or above with 2dsparse index version 1 |
425
|
|
|
* to use Point, LineString and Polygon. |
426
|
|
|
* |
427
|
|
|
* @link http://docs.mongodb.org/manual/core/2dsphere/#polygon |
428
|
|
|
* @param string $field |
429
|
|
|
* @param array $lineRingsArray array of line rings |
430
|
|
|
* @return \Sokil\Mongo\Document |
431
|
|
|
*/ |
432
|
|
|
public function setPolygon($field, array $lineRingsArray) |
433
|
|
|
{ |
434
|
|
|
return $this->setGeometry( |
435
|
|
|
$field, |
436
|
|
|
new \GeoJson\Geometry\Polygon($lineRingsArray) |
437
|
|
|
); |
438
|
|
|
} |
439
|
|
|
|
440
|
|
|
/** |
441
|
|
|
* Set multi point as array of points |
442
|
|
|
* |
443
|
|
|
* Requires MongoDB version 2.6 or above with 2dsparse index version 2 |
444
|
|
|
* to use MultiPoint, MultiLineString, MultiPolygon and GeometryCollection. |
445
|
|
|
* |
446
|
|
|
* @link http://docs.mongodb.org/manual/core/2dsphere/#multipoint |
447
|
|
|
* @param string $field |
448
|
|
|
* @param array $pointArray array of point arrays |
449
|
|
|
* @return \Sokil\Mongo\Document |
450
|
|
|
*/ |
451
|
|
|
public function setMultiPoint($field, $pointArray) |
452
|
|
|
{ |
453
|
|
|
return $this->setGeometry( |
454
|
|
|
$field, |
455
|
|
|
new \GeoJson\Geometry\MultiPoint($pointArray) |
456
|
|
|
); |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* Set multi line string as array of line strings |
461
|
|
|
* |
462
|
|
|
* Requires MongoDB version 2.6 or above with 2dsparse index version 2 |
463
|
|
|
* to use MultiPoint, MultiLineString, MultiPolygon and GeometryCollection. |
464
|
|
|
* |
465
|
|
|
* http://docs.mongodb.org/manual/core/2dsphere/#multilinestring |
466
|
|
|
* @param string $field |
467
|
|
|
* @param array $lineStringArray array of line strings |
468
|
|
|
* @return \Sokil\Mongo\Document |
469
|
|
|
*/ |
470
|
|
|
public function setMultiLineString($field, $lineStringArray) |
471
|
|
|
{ |
472
|
|
|
return $this->setGeometry( |
473
|
|
|
$field, |
474
|
|
|
new \GeoJson\Geometry\MultiLineString($lineStringArray) |
475
|
|
|
); |
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
/** |
479
|
|
|
* Set multy polygon as array of polygons. |
480
|
|
|
* |
481
|
|
|
* Polygon is array of line rings. |
482
|
|
|
* Line ring is closed line string (first and last point same). |
483
|
|
|
* Line string is array of points. |
484
|
|
|
* |
485
|
|
|
* Requires MongoDB version 2.6 or above with 2dsparse index version 2 |
486
|
|
|
* to use MultiPoint, MultiLineString, MultiPolygon and GeometryCollection. |
487
|
|
|
* |
488
|
|
|
* @link http://docs.mongodb.org/manual/core/2dsphere/#multipolygon |
489
|
|
|
* @param string $field |
490
|
|
|
* @param array $polygonsArray array of polygons |
491
|
|
|
* @return \Sokil\Mongo\Document |
492
|
|
|
*/ |
493
|
|
|
public function setMultyPolygon($field, array $polygonsArray) |
494
|
|
|
{ |
495
|
|
|
return $this->setGeometry( |
496
|
|
|
$field, |
497
|
|
|
new \GeoJson\Geometry\MultiPolygon($polygonsArray) |
498
|
|
|
); |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
/** |
502
|
|
|
* Set collection of different geometries |
503
|
|
|
* |
504
|
|
|
* Requires MongoDB version 2.6 or above with 2dsparse index version 2 |
505
|
|
|
* to use MultiPoint, MultiLineString, MultiPolygon and GeometryCollection. |
506
|
|
|
* |
507
|
|
|
* @link http://docs.mongodb.org/manual/core/2dsphere/#geometrycollection |
508
|
|
|
* @param string $field |
509
|
|
|
* @param array $geometryCollection |
510
|
|
|
* @return \Sokil\Mongo\Document |
511
|
|
|
*/ |
512
|
|
|
public function setGeometryCollection($field, array $geometryCollection) |
513
|
|
|
{ |
514
|
|
|
return $this->setGeometry( |
515
|
|
|
$field, |
516
|
|
|
new \GeoJson\Geometry\GeometryCollection($geometryCollection) |
517
|
|
|
); |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
/** |
521
|
|
|
* Check if document belongs to specified collection |
522
|
|
|
* |
523
|
|
|
* @deprecated since 1.12.8 Use Collection::hasDocument() |
524
|
|
|
* @param \Sokil\Mongo\Collection $collection collection instance |
525
|
|
|
* @return boolean |
526
|
|
|
*/ |
527
|
|
|
public function belongsToCollection(Collection $collection) |
528
|
|
|
{ |
529
|
|
|
return $collection->hasDocument($this); |
530
|
|
|
} |
531
|
|
|
|
532
|
|
|
/** |
533
|
|
|
* Override in child class to define relations |
534
|
|
|
* @return array relation description |
535
|
|
|
*/ |
536
|
|
|
protected function relations() |
537
|
|
|
{ |
538
|
|
|
// [relationName => [relationType, targetCollection, reference], ...] |
|
|
|
|
539
|
|
|
return array(); |
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
/** |
543
|
|
|
* Relation definition through mapping is more prior to defined in class |
544
|
|
|
* @return array definition of relations |
545
|
|
|
*/ |
546
|
|
|
public function getRelationDefinition() |
547
|
|
|
{ |
548
|
|
|
$relations = $this->getOption('relations'); |
549
|
|
|
if(!is_array($relations)) { |
550
|
|
|
return $this->relations(); |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
return $relations + $this->relations(); |
554
|
|
|
} |
555
|
|
|
|
556
|
|
|
/** |
557
|
|
|
* |
558
|
|
|
* @return \Sokil\Mongo\Document\RelationManager |
559
|
|
|
*/ |
560
|
|
|
private function getRelationManager() |
561
|
|
|
{ |
562
|
|
|
if($this->relationManager) { |
563
|
|
|
return $this->relationManager; |
564
|
|
|
} |
565
|
|
|
|
566
|
|
|
$this->relationManager = new RelationManager($this); |
567
|
|
|
|
568
|
|
|
return $this->relationManager; |
569
|
|
|
} |
570
|
|
|
|
571
|
|
|
/** |
572
|
|
|
* Get related documents |
573
|
|
|
* @param string $relationName |
574
|
|
|
* @return array|\Sokil\Mongo\Document related document or array of documents |
575
|
|
|
*/ |
576
|
|
|
public function getRelated($relationName) |
577
|
|
|
{ |
578
|
|
|
return $this->getRelationManager()->getRelated($relationName); |
579
|
|
|
} |
580
|
|
|
|
581
|
|
|
public function addRelation($relationName, Document $document) |
582
|
|
|
{ |
583
|
|
|
$this->getRelationManager()->addRelation($relationName, $document); |
584
|
|
|
|
585
|
|
|
return $this; |
586
|
|
|
} |
587
|
|
|
|
588
|
|
|
public function removeRelation($relationName, Document $document = null) |
589
|
|
|
{ |
590
|
|
|
$this->getRelationManager()->removeRelation($relationName, $document); |
591
|
|
|
|
592
|
|
|
return $this; |
593
|
|
|
} |
594
|
|
|
|
595
|
|
|
/** |
596
|
|
|
* Manually trigger defined events |
597
|
|
|
* @param string $eventName event name |
598
|
|
|
* @return \Sokil\Mongo\Event |
599
|
|
|
*/ |
600
|
|
|
public function triggerEvent($eventName, Event $event = null) |
601
|
|
|
{ |
602
|
|
|
if(!$event) { |
603
|
|
|
$event = new Event; |
604
|
|
|
} |
605
|
|
|
|
606
|
|
|
$event->setTarget($this); |
607
|
|
|
|
608
|
|
|
return $this->eventDispatcher->dispatch($eventName, $event); |
609
|
|
|
} |
610
|
|
|
|
611
|
|
|
/** |
612
|
|
|
* Attach event handler |
613
|
|
|
* @param string $event event name |
614
|
|
|
* @param callable|array|string $handler event handler |
615
|
|
|
* @return \Sokil\Mongo\Document |
616
|
|
|
*/ |
617
|
|
|
public function attachEvent($event, $handler, $priority = 0) |
618
|
|
|
{ |
619
|
|
|
$this->eventDispatcher->addListener($event, $handler, $priority); |
620
|
|
|
return $this; |
621
|
|
|
} |
622
|
|
|
|
623
|
|
|
/** |
624
|
|
|
* Check if event attached |
625
|
|
|
* |
626
|
|
|
* @param string $event event name |
627
|
|
|
* @return bool |
628
|
|
|
*/ |
629
|
|
|
public function hasEvent($event) |
630
|
|
|
{ |
631
|
|
|
return $this->eventDispatcher->hasListeners($event); |
632
|
|
|
} |
633
|
|
|
|
634
|
|
|
public function getId() |
635
|
|
|
{ |
636
|
|
|
return $this->get('_id'); |
637
|
|
|
} |
638
|
|
|
|
639
|
|
|
/** |
640
|
|
|
* Used to define id of stored document. This id must be already present in db |
641
|
|
|
* |
642
|
|
|
* @param \MongoId|string $id id of document |
643
|
|
|
* @return \Sokil\Mongo\Document |
644
|
|
|
*/ |
645
|
|
|
public function defineId($id) |
646
|
|
|
{ |
647
|
|
|
|
648
|
|
|
if ($id instanceof \MongoId) { |
649
|
|
|
$this->_data['_id'] = $id; |
650
|
|
|
return $this; |
651
|
|
|
} |
652
|
|
|
|
653
|
|
|
try { |
654
|
|
|
$this->_data['_id'] = new \MongoId($id); |
655
|
|
|
} catch (\MongoException $e) { |
656
|
|
|
$this->_data['_id'] = $id; |
657
|
|
|
} |
658
|
|
|
|
659
|
|
|
return $this; |
660
|
|
|
} |
661
|
|
|
|
662
|
|
|
/* |
663
|
|
|
* Used to define id of unstored document. This db is manual |
664
|
|
|
*/ |
665
|
|
|
|
666
|
|
|
public function setId($id) |
667
|
|
|
{ |
668
|
|
|
|
669
|
|
|
if ($id instanceof \MongoId) { |
670
|
|
|
return $this->set('_id', $id); |
671
|
|
|
} |
672
|
|
|
|
673
|
|
|
try { |
674
|
|
|
return $this->set('_id', new \MongoId($id)); |
675
|
|
|
} catch (\MongoException $e) { |
676
|
|
|
return $this->set('_id', $id); |
677
|
|
|
} |
678
|
|
|
} |
679
|
|
|
|
680
|
|
|
public function isStored() |
681
|
|
|
{ |
682
|
|
|
return $this->get('_id') && !$this->isModified('_id'); |
683
|
|
|
} |
684
|
|
|
|
685
|
|
|
public function setScenario($scenario) |
686
|
|
|
{ |
687
|
|
|
$this->scenario = $scenario; |
688
|
|
|
return $this; |
689
|
|
|
} |
690
|
|
|
|
691
|
|
|
public function getScenario() |
692
|
|
|
{ |
693
|
|
|
return $this->scenario; |
694
|
|
|
} |
695
|
|
|
|
696
|
|
|
public function setNoScenario() |
697
|
|
|
{ |
698
|
|
|
$this->scenario = null; |
699
|
|
|
return $this; |
700
|
|
|
} |
701
|
|
|
|
702
|
|
|
public function isScenario($scenario) |
703
|
|
|
{ |
704
|
|
|
return $scenario === $this->scenario; |
705
|
|
|
} |
706
|
|
|
|
707
|
|
|
public function rules() |
708
|
|
|
{ |
709
|
|
|
return array(); |
710
|
|
|
} |
711
|
|
|
|
712
|
|
|
private function getValidatorClassNameByRuleName($ruleName) |
713
|
|
|
{ |
714
|
|
|
if(false !== strpos($ruleName, '_')) { |
715
|
|
|
$className = implode('', array_map('ucfirst', explode('_', strtolower($ruleName)))); |
716
|
|
|
} else { |
717
|
|
|
$className = ucfirst(strtolower($ruleName)); |
718
|
|
|
} |
719
|
|
|
|
720
|
|
|
foreach ($this->validatorNamespaces as $namespace) { |
721
|
|
|
$fullyQualifiedClassName = $namespace . '\\' . $className . 'Validator'; |
722
|
|
|
if (class_exists($fullyQualifiedClassName)) { |
723
|
|
|
return $fullyQualifiedClassName; |
724
|
|
|
} |
725
|
|
|
} |
726
|
|
|
|
727
|
|
|
throw new Exception('Validator with name ' . $ruleName . ' not found'); |
728
|
|
|
} |
729
|
|
|
|
730
|
|
|
/** |
731
|
|
|
* check if filled model params is valid |
732
|
|
|
* @return boolean |
733
|
|
|
*/ |
734
|
|
|
public function isValid() |
735
|
|
|
{ |
736
|
|
|
$this->errors = array(); |
737
|
|
|
|
738
|
|
|
foreach ($this->rules() as $rule) { |
739
|
|
|
$fields = array_map('trim', explode(',', $rule[0])); |
740
|
|
|
$ruleName = $rule[1]; |
741
|
|
|
$params = array_slice($rule, 2); |
742
|
|
|
|
743
|
|
|
// check scenario |
744
|
|
View Code Duplication |
if (!empty($rule['on'])) { |
|
|
|
|
745
|
|
|
$onScenarios = explode(',', $rule['on']); |
746
|
|
|
if (!in_array($this->getScenario(), $onScenarios)) { |
747
|
|
|
continue; |
748
|
|
|
} |
749
|
|
|
} |
750
|
|
|
|
751
|
|
View Code Duplication |
if (!empty($rule['except'])) { |
|
|
|
|
752
|
|
|
$exceptScenarios = explode(',', $rule['except']); |
753
|
|
|
if (in_array($this->getScenario(), $exceptScenarios)) { |
754
|
|
|
continue; |
755
|
|
|
} |
756
|
|
|
} |
757
|
|
|
|
758
|
|
|
if (method_exists($this, $ruleName)) { |
759
|
|
|
// method |
760
|
|
|
foreach ($fields as $field) { |
761
|
|
|
$this->{$ruleName}($field, $params); |
762
|
|
|
} |
763
|
|
|
} else { |
764
|
|
|
// validator class |
765
|
|
|
$validatorClassName = $this->getValidatorClassNameByRuleName($ruleName); |
766
|
|
|
|
767
|
|
|
/* @var $validator \Sokil\Mongo\Validator */ |
768
|
|
|
$validator = new $validatorClassName; |
769
|
|
|
if (!$validator instanceof \Sokil\Mongo\Validator) { |
770
|
|
|
throw new Exception('Validator class must implement \Sokil\Mongo\Validator class'); |
771
|
|
|
} |
772
|
|
|
|
773
|
|
|
$validator->validate($this, $fields, $params); |
774
|
|
|
} |
775
|
|
|
} |
776
|
|
|
|
777
|
|
|
return !$this->hasErrors(); |
778
|
|
|
} |
779
|
|
|
|
780
|
|
|
/** |
781
|
|
|
* |
782
|
|
|
* @throws \Sokil\Mongo\Document\InvalidDocumentException |
783
|
|
|
* @return \Sokil\Mongo\Document |
784
|
|
|
*/ |
785
|
|
|
public function validate() |
786
|
|
|
{ |
787
|
|
|
if($this->triggerEvent('beforeValidate')->isCancelled()) { |
788
|
|
|
return $this; |
789
|
|
|
} |
790
|
|
|
|
791
|
|
|
if (!$this->isValid()) { |
792
|
|
|
$exception = new InvalidDocumentException('Document not valid'); |
793
|
|
|
$exception->setDocument($this); |
794
|
|
|
|
795
|
|
|
$this->triggerEvent('validateError'); |
796
|
|
|
|
797
|
|
|
throw $exception; |
798
|
|
|
} |
799
|
|
|
|
800
|
|
|
$this->triggerEvent('afterValidate'); |
801
|
|
|
|
802
|
|
|
return $this; |
803
|
|
|
} |
804
|
|
|
|
805
|
|
|
public function hasErrors() |
806
|
|
|
{ |
807
|
|
|
return ($this->errors || $this->triggeredErrors); |
|
|
|
|
808
|
|
|
} |
809
|
|
|
|
810
|
|
|
/** |
811
|
|
|
* get list of validation errors |
812
|
|
|
* |
813
|
|
|
* Format: $errors['fieldName']['rule'] = 'message'; |
814
|
|
|
* |
815
|
|
|
* @return array list of validation errors |
816
|
|
|
*/ |
817
|
|
|
public function getErrors() |
818
|
|
|
{ |
819
|
|
|
return array_merge_recursive($this->errors, $this->triggeredErrors); |
820
|
|
|
} |
821
|
|
|
|
822
|
|
|
/** |
823
|
|
|
* Add validator error from validator classes and methods. This error |
824
|
|
|
* reset on every revalidation |
825
|
|
|
* |
826
|
|
|
* @param string $fieldName dot-notated field name |
827
|
|
|
* @param string $ruleName name of validation rule |
828
|
|
|
* @param string $message error message |
829
|
|
|
* @return \Sokil\Mongo\Document |
830
|
|
|
*/ |
831
|
|
|
public function addError($fieldName, $ruleName, $message) |
832
|
|
|
{ |
833
|
|
|
$this->errors[$fieldName][$ruleName] = $message; |
834
|
|
|
|
835
|
|
|
// Deprecated. Related to bug when suffix not removed from class. |
836
|
|
|
// Added for back compatibility and will be removed in next versions |
837
|
|
|
$this->errors[$fieldName][$ruleName . 'validator'] = $message; |
838
|
|
|
|
839
|
|
|
return $this; |
840
|
|
|
} |
841
|
|
|
|
842
|
|
|
/** |
843
|
|
|
* Add errors |
844
|
|
|
* |
845
|
|
|
* @param array $errors |
846
|
|
|
* @return \Sokil\Mongo\Document |
847
|
|
|
*/ |
848
|
|
|
public function addErrors(array $errors) |
849
|
|
|
{ |
850
|
|
|
$this->errors = array_merge_recursive($this->errors, $errors); |
851
|
|
|
return $this; |
852
|
|
|
} |
853
|
|
|
|
854
|
|
|
/** |
855
|
|
|
* Add custom error which not reset after validation |
856
|
|
|
* |
857
|
|
|
* @param type $fieldName |
858
|
|
|
* @param type $ruleName |
859
|
|
|
* @param type $message |
860
|
|
|
* @return \Sokil\Mongo\Document |
861
|
|
|
*/ |
862
|
|
|
public function triggerError($fieldName, $ruleName, $message) |
863
|
|
|
{ |
864
|
|
|
$this->triggeredErrors[$fieldName][$ruleName] = $message; |
865
|
|
|
return $this; |
866
|
|
|
} |
867
|
|
|
|
868
|
|
|
/** |
869
|
|
|
* Add custom errors |
870
|
|
|
* |
871
|
|
|
* @param array $errors |
872
|
|
|
* @return \Sokil\Mongo\Document |
873
|
|
|
*/ |
874
|
|
|
public function triggerErrors(array $errors) |
875
|
|
|
{ |
876
|
|
|
$this->triggeredErrors = array_merge_recursive($this->triggeredErrors, $errors); |
877
|
|
|
return $this; |
878
|
|
|
} |
879
|
|
|
|
880
|
|
|
/** |
881
|
|
|
* Remove custom errors |
882
|
|
|
* |
883
|
|
|
* @return \Sokil\Mongo\Document |
884
|
|
|
*/ |
885
|
|
|
public function clearTriggeredErrors() |
886
|
|
|
{ |
887
|
|
|
$this->triggeredErrors = array(); |
888
|
|
|
return $this; |
889
|
|
|
} |
890
|
|
|
|
891
|
|
|
public function behaviors() |
892
|
|
|
{ |
893
|
|
|
return array(); |
894
|
|
|
} |
895
|
|
|
|
896
|
|
|
public function attachBehaviors(array $behaviors) |
897
|
|
|
{ |
898
|
|
|
foreach ($behaviors as $name => $behavior) { |
899
|
|
|
$this->attachBehavior($name, $behavior); |
900
|
|
|
} |
901
|
|
|
|
902
|
|
|
return $this; |
903
|
|
|
} |
904
|
|
|
|
905
|
|
|
/** |
906
|
|
|
* |
907
|
|
|
* @param string $name unique name of attached behavior |
908
|
|
|
* @param string|array|\Sokil\Mongo\Behavior $behavior Behavior instance or behavior definition |
909
|
|
|
* @return \Sokil\Mongo\Document |
910
|
|
|
* @throws Exception |
911
|
|
|
*/ |
912
|
|
|
public function attachBehavior($name, $behavior) |
913
|
|
|
{ |
914
|
|
|
if(is_string($behavior)) { |
915
|
|
|
// behavior defined as string |
916
|
|
|
$className = $behavior; |
917
|
|
|
$behavior = new $className(); |
918
|
|
|
} elseif(is_array($behavior)) { |
919
|
|
|
// behavior defined as array |
920
|
|
|
if (empty($behavior['class'])) { |
921
|
|
|
throw new Exception('Behavior class not specified'); |
922
|
|
|
} |
923
|
|
|
$className = $behavior['class']; |
924
|
|
|
unset($behavior['class']); |
925
|
|
|
$behavior = new $className($behavior); |
926
|
|
|
} elseif (!($behavior instanceof Behavior)) { |
927
|
|
|
// behavior bust be Behavior instance, but something else found |
928
|
|
|
throw new Exception('Wrong behavior specified with name ' . $name); |
929
|
|
|
} |
930
|
|
|
|
931
|
|
|
$behavior->setOwner($this); |
932
|
|
|
|
933
|
|
|
$this->behaviors[$name] = $behavior; |
934
|
|
|
|
935
|
|
|
return $this; |
936
|
|
|
} |
937
|
|
|
|
938
|
|
|
public function clearBehaviors() |
939
|
|
|
{ |
940
|
|
|
$this->behaviors = array(); |
941
|
|
|
return $this; |
942
|
|
|
} |
943
|
|
|
|
944
|
|
|
public function getOperator() |
945
|
|
|
{ |
946
|
|
|
return $this->operator; |
947
|
|
|
} |
948
|
|
|
|
949
|
|
|
public function isModificationOperatorDefined() |
950
|
|
|
{ |
951
|
|
|
return $this->operator->isDefined(); |
952
|
|
|
} |
953
|
|
|
|
954
|
|
|
/** |
955
|
|
|
* Update value in local cache and in DB |
956
|
|
|
* |
957
|
|
|
* @param string $fieldName point-delimited field name |
958
|
|
|
* @param mixed $value value to store |
959
|
|
|
* @return \Sokil\Mongo\Document |
960
|
|
|
*/ |
961
|
|
|
public function set($fieldName, $value) |
962
|
|
|
{ |
963
|
|
|
parent::set($fieldName, $value); |
964
|
|
|
|
965
|
|
|
// if document saved - save through update |
966
|
|
|
if ($this->getId()) { |
967
|
|
|
$this->operator->set($fieldName, $value); |
968
|
|
|
} |
969
|
|
|
|
970
|
|
|
return $this; |
971
|
|
|
} |
972
|
|
|
|
973
|
|
|
/** |
974
|
|
|
* Remove field |
975
|
|
|
* |
976
|
|
|
* @param string $fieldName field name |
977
|
|
|
* @return \Sokil\Mongo\Document |
978
|
|
|
*/ |
979
|
|
|
public function unsetField($fieldName) |
980
|
|
|
{ |
981
|
|
|
if (!$this->has($fieldName)) { |
982
|
|
|
return $this; |
983
|
|
|
} |
984
|
|
|
|
985
|
|
|
parent::unsetField($fieldName); |
986
|
|
|
|
987
|
|
|
if ($this->getId()) { |
988
|
|
|
$this->operator->unsetField($fieldName); |
989
|
|
|
} |
990
|
|
|
|
991
|
|
|
return $this; |
992
|
|
|
} |
993
|
|
|
|
994
|
|
|
public function __unset($fieldName) |
995
|
|
|
{ |
996
|
|
|
$this->unsetField($fieldName); |
997
|
|
|
} |
998
|
|
|
|
999
|
|
|
public function merge(array $data) |
1000
|
|
|
{ |
1001
|
|
|
if ($this->isStored()) { |
1002
|
|
|
foreach ($data as $fieldName => $value) { |
1003
|
|
|
$this->set($fieldName, $value); |
1004
|
|
|
} |
1005
|
|
|
} else { |
1006
|
|
|
parent::merge($data); |
1007
|
|
|
} |
1008
|
|
|
|
1009
|
|
|
return $this; |
1010
|
|
|
} |
1011
|
|
|
|
1012
|
|
|
/** |
1013
|
|
|
* If field not exist - set value. |
1014
|
|
|
* If field exists and is not array - convert to array and append |
1015
|
|
|
* If field -s array - append |
1016
|
|
|
* |
1017
|
|
|
* @param type $selector |
1018
|
|
|
* @param type $value |
1019
|
|
|
* @return \Sokil\Mongo\Structure |
1020
|
|
|
*/ |
1021
|
|
|
public function append($selector, $value) |
1022
|
|
|
{ |
1023
|
|
|
parent::append($selector, $value); |
1024
|
|
|
|
1025
|
|
|
// if document saved - save through update |
1026
|
|
|
if ($this->getId()) { |
1027
|
|
|
$this->operator->set($selector, $this->get($selector)); |
1028
|
|
|
} |
1029
|
|
|
|
1030
|
|
|
return $this; |
1031
|
|
|
} |
1032
|
|
|
|
1033
|
|
|
/** |
1034
|
|
|
* Push argument as single element to field value |
1035
|
|
|
* |
1036
|
|
|
* @param string $fieldName |
1037
|
|
|
* @param mixed $value |
1038
|
|
|
* @return \Sokil\Mongo\Document |
1039
|
|
|
*/ |
1040
|
|
|
public function push($fieldName, $value) |
1041
|
|
|
{ |
1042
|
|
|
$oldValue = $this->get($fieldName); |
1043
|
|
|
|
1044
|
|
|
if ($value instanceof Structure) { |
1045
|
|
|
$value = $value->toArray(); |
1046
|
|
|
} |
1047
|
|
|
|
1048
|
|
|
// field not exists |
1049
|
|
|
if (!$oldValue) { |
1050
|
|
|
if ($this->getId()) { |
1051
|
|
|
$this->operator->push($fieldName, $value); |
1052
|
|
|
} |
1053
|
|
|
$value = array($value); |
1054
|
|
|
} // field already exist and has single value |
1055
|
|
View Code Duplication |
elseif (!is_array($oldValue)) { |
|
|
|
|
1056
|
|
|
$value = array_merge((array) $oldValue, array($value)); |
1057
|
|
|
if ($this->getId()) { |
1058
|
|
|
$this->operator->set($fieldName, $value); |
1059
|
|
|
} |
1060
|
|
|
} // field exists and is array |
1061
|
|
|
else { |
1062
|
|
|
if ($this->getId()) { |
1063
|
|
|
// check if array because previous $set operation on single value was executed |
1064
|
|
|
$setValue = $this->operator->get('$set', $fieldName); |
1065
|
|
|
if ($setValue) { |
1066
|
|
|
$setValue[] = $value; |
1067
|
|
|
$this->operator->set($fieldName, $setValue); |
1068
|
|
|
} else { |
1069
|
|
|
$this->operator->push($fieldName, $value); |
1070
|
|
|
} |
1071
|
|
|
} |
1072
|
|
|
$value = array_merge($oldValue, array($value)); |
1073
|
|
|
} |
1074
|
|
|
|
1075
|
|
|
// update local data |
1076
|
|
|
parent::set($fieldName, $value); |
|
|
|
|
1077
|
|
|
|
1078
|
|
|
return $this; |
1079
|
|
|
} |
1080
|
|
|
|
1081
|
|
|
/** |
1082
|
|
|
* Push each element of argument's array as single element to field value |
1083
|
|
|
* |
1084
|
|
|
* @param string $fieldName |
1085
|
|
|
* @param array $values |
1086
|
|
|
* @return \Sokil\Mongo\Document |
1087
|
|
|
*/ |
1088
|
|
|
public function pushEach($fieldName, array $values) |
1089
|
|
|
{ |
1090
|
|
|
$oldValue = $this->get($fieldName); |
1091
|
|
|
|
1092
|
|
|
if ($this->getId()) { |
1093
|
|
|
if (!$oldValue) { |
1094
|
|
|
$this->operator->pushEach($fieldName, $values); |
1095
|
|
View Code Duplication |
} elseif (!is_array($oldValue)) { |
|
|
|
|
1096
|
|
|
$values = array_merge((array) $oldValue, $values); |
1097
|
|
|
$this->operator->set($fieldName, $values); |
1098
|
|
|
} else { |
1099
|
|
|
$this->operator->pushEach($fieldName, $values); |
1100
|
|
|
$values = array_merge($oldValue, $values); |
1101
|
|
|
} |
1102
|
|
|
} else { |
1103
|
|
|
if ($oldValue) { |
1104
|
|
|
$values = array_merge((array) $oldValue, $values); |
1105
|
|
|
} |
1106
|
|
|
} |
1107
|
|
|
|
1108
|
|
|
// update local data |
1109
|
|
|
parent::set($fieldName, $values); |
|
|
|
|
1110
|
|
|
|
1111
|
|
|
return $this; |
1112
|
|
|
} |
1113
|
|
|
|
1114
|
|
|
public function addToSet($fieldName, $value) |
1115
|
|
|
{ |
1116
|
|
|
$oldValues = $this->get($fieldName); |
1117
|
|
|
if (!$oldValues) { |
1118
|
|
|
$newValue = [$value]; |
1119
|
|
|
$this->operator->addToSet($fieldName, $value); |
1120
|
|
|
} elseif (!is_array($value)) { |
1121
|
|
|
if ($oldValues === $value) { |
1122
|
|
|
return $this; |
1123
|
|
|
} |
1124
|
|
|
$newValue = array($oldValues, $value); |
1125
|
|
|
$this->operator->set($fieldName, $newValue); |
1126
|
|
|
} else { |
1127
|
|
|
if (in_array($value, $oldValues)) { |
1128
|
|
|
return $this; |
1129
|
|
|
} |
1130
|
|
|
$newValue = array_merge($oldValues, array($value)); |
1131
|
|
|
$this->operator->addToSet($fieldName, $value); |
1132
|
|
|
} |
1133
|
|
|
|
1134
|
|
|
parent::set($fieldName, $newValue); |
|
|
|
|
1135
|
|
|
|
1136
|
|
|
return $this; |
1137
|
|
|
} |
1138
|
|
|
|
1139
|
|
|
public function addToSetEach($fieldName, array $values) |
|
|
|
|
1140
|
|
|
{ |
1141
|
|
|
|
1142
|
|
|
} |
1143
|
|
|
|
1144
|
|
|
/** |
1145
|
|
|
* Removes from an existing array all instances of a value or |
1146
|
|
|
* values that match a specified query |
1147
|
|
|
* |
1148
|
|
|
* @param integer|string|array|\Sokil\Mongo\Expression|callable $expression |
1149
|
|
|
* @param mixed|\Sokil\Mongo\Expression|callable $value |
1150
|
|
|
* @return \Sokil\Mongo\Document |
1151
|
|
|
*/ |
1152
|
|
|
public function pull($expression, $value = null) |
1153
|
|
|
{ |
1154
|
|
|
$this->operator->pull($expression, $value); |
1155
|
|
|
return $this; |
1156
|
|
|
} |
1157
|
|
|
|
1158
|
|
|
public function increment($fieldName, $value = 1) |
1159
|
|
|
{ |
1160
|
|
|
parent::set($fieldName, (int) $this->get($fieldName) + $value); |
|
|
|
|
1161
|
|
|
|
1162
|
|
|
if ($this->getId()) { |
1163
|
|
|
$this->operator->increment($fieldName, $value); |
1164
|
|
|
} |
1165
|
|
|
|
1166
|
|
|
|
1167
|
|
|
return $this; |
1168
|
|
|
} |
1169
|
|
|
|
1170
|
|
|
public function decrement($fieldName, $value = 1) |
1171
|
|
|
{ |
1172
|
|
|
return $this->increment($fieldName, -1 * $value); |
1173
|
|
|
} |
1174
|
|
|
|
1175
|
|
View Code Duplication |
public function bitwiceAnd($field, $value) |
|
|
|
|
1176
|
|
|
{ |
1177
|
|
|
parent::set($field, (int) $this->get($field) & $value); |
|
|
|
|
1178
|
|
|
|
1179
|
|
|
if ($this->getId()) { |
1180
|
|
|
$this->operator->bitwiceAnd($field, $value); |
1181
|
|
|
} |
1182
|
|
|
|
1183
|
|
|
return $this; |
1184
|
|
|
} |
1185
|
|
|
|
1186
|
|
View Code Duplication |
public function bitwiceOr($field, $value) |
|
|
|
|
1187
|
|
|
{ |
1188
|
|
|
parent::set($field, (int) $this->get($field) | $value); |
|
|
|
|
1189
|
|
|
|
1190
|
|
|
if ($this->getId()) { |
1191
|
|
|
$this->operator->bitwiceOr($field, $value); |
1192
|
|
|
} |
1193
|
|
|
|
1194
|
|
|
return $this; |
1195
|
|
|
} |
1196
|
|
|
|
1197
|
|
|
public function bitwiceXor($field, $value) |
1198
|
|
|
{ |
1199
|
|
|
$oldFieldValue = (int) $this->get($field); |
1200
|
|
|
$newValue = $oldFieldValue ^ $value; |
1201
|
|
|
|
1202
|
|
|
parent::set($field, $newValue); |
|
|
|
|
1203
|
|
|
|
1204
|
|
|
if ($this->getId()) { |
1205
|
|
|
if(version_compare($this->getCollection()->getDatabase()->getClient()->getDbVersion(), '2.6', '>=')) { |
1206
|
|
|
$this->operator->bitwiceXor($field, $value); |
1207
|
|
|
} else { |
1208
|
|
|
$this->operator->set($field, $newValue); |
1209
|
|
|
} |
1210
|
|
|
} |
1211
|
|
|
|
1212
|
|
|
return $this; |
1213
|
|
|
} |
1214
|
|
|
|
1215
|
|
|
public function save($validate = true) |
1216
|
|
|
{ |
1217
|
|
|
// save document |
1218
|
|
|
// if document already in db and not modified - skip this method |
1219
|
|
|
if (!$this->isSaveRequired()) { |
1220
|
|
|
return $this; |
1221
|
|
|
} |
1222
|
|
|
|
1223
|
|
|
if ($validate) { |
1224
|
|
|
$this->validate(); |
1225
|
|
|
} |
1226
|
|
|
|
1227
|
|
|
// handle beforeSave event |
1228
|
|
|
if($this->triggerEvent('beforeSave')->isCancelled()) { |
1229
|
|
|
return $this; |
1230
|
|
|
} |
1231
|
|
|
if ($this->isStored()) { |
1232
|
|
|
if($this->triggerEvent('beforeUpdate')->isCancelled()) { |
1233
|
|
|
return $this; |
1234
|
|
|
} |
1235
|
|
|
|
1236
|
|
|
// locking |
1237
|
|
|
$query = array('_id' => $this->getId()); |
1238
|
|
|
if ($this->getOption('lock') === Definition::LOCK_OPTIMISTIC) { |
1239
|
|
|
$query['__version__'] = $this->get('__version__'); |
1240
|
|
|
$this->getOperator()->increment('__version__'); |
1241
|
|
|
} |
1242
|
|
|
|
1243
|
|
|
// update |
1244
|
|
|
$status = $this->collection |
1245
|
|
|
->getMongoCollection() |
1246
|
|
|
->update( |
1247
|
|
|
$query, |
1248
|
|
|
$this->getOperator()->toArray() |
1249
|
|
|
); |
1250
|
|
|
|
1251
|
|
|
// check update status |
1252
|
|
View Code Duplication |
if ($status['ok'] != 1) { |
|
|
|
|
1253
|
|
|
throw new \Sokil\Mongo\Exception(sprintf( |
1254
|
|
|
'Update error: %s: %s', |
1255
|
|
|
$status['err'], |
1256
|
|
|
$status['errmsg'] |
1257
|
|
|
)); |
1258
|
|
|
} |
1259
|
|
|
|
1260
|
|
|
// check if document modified due to specified lock |
1261
|
|
|
if ($this->getOption('lock') === Definition::LOCK_OPTIMISTIC) { |
1262
|
|
|
if($status['n'] === 0) { |
1263
|
|
|
throw new OptimisticLockFailureException; |
1264
|
|
|
} |
1265
|
|
|
} |
1266
|
|
|
|
1267
|
|
|
if ($this->getOperator()->isReloadRequired()) { |
1268
|
|
|
$this->refresh(); |
1269
|
|
|
} else { |
1270
|
|
|
$this->getOperator()->reset(); |
1271
|
|
|
} |
1272
|
|
|
|
1273
|
|
|
$this->triggerEvent('afterUpdate'); |
1274
|
|
|
} else { |
1275
|
|
|
if($this->triggerEvent('beforeInsert')->isCancelled()) { |
1276
|
|
|
return $this; |
1277
|
|
|
} |
1278
|
|
|
|
1279
|
|
|
$document = $this->toArray(); |
1280
|
|
|
|
1281
|
|
|
$this |
1282
|
|
|
->collection |
1283
|
|
|
->getMongoCollection() |
1284
|
|
|
->insert($document); |
1285
|
|
|
|
1286
|
|
|
// set id |
1287
|
|
|
$this->defineId($document['_id']); |
1288
|
|
|
|
1289
|
|
|
// after insert event |
1290
|
|
|
$this->triggerEvent('afterInsert'); |
1291
|
|
|
} |
1292
|
|
|
|
1293
|
|
|
// handle afterSave event |
1294
|
|
|
$this->triggerEvent('afterSave'); |
1295
|
|
|
|
1296
|
|
|
$this->apply(); |
1297
|
|
|
|
1298
|
|
|
return $this; |
1299
|
|
|
} |
1300
|
|
|
|
1301
|
|
|
public function isSaveRequired() |
1302
|
|
|
{ |
1303
|
|
|
return !$this->isStored() || $this->isModified() || $this->isModificationOperatorDefined(); |
1304
|
|
|
} |
1305
|
|
|
|
1306
|
|
|
public function delete() |
1307
|
|
|
{ |
1308
|
|
|
if ($this->triggerEvent('beforeDelete')->isCancelled()) { |
1309
|
|
|
return $this; |
1310
|
|
|
} |
1311
|
|
|
|
1312
|
|
|
$status = $this->collection->getMongoCollection()->remove(array( |
1313
|
|
|
'_id' => $this->getId(), |
1314
|
|
|
)); |
1315
|
|
|
|
1316
|
|
View Code Duplication |
if(true !== $status && $status['ok'] != 1) { |
|
|
|
|
1317
|
|
|
throw new \Sokil\Mongo\Exception(sprintf('Delete document error: %s', $status['err'])); |
1318
|
|
|
} |
1319
|
|
|
|
1320
|
|
|
$this->triggerEvent('afterDelete'); |
1321
|
|
|
|
1322
|
|
|
// drop from document's pool |
1323
|
|
|
$this->getCollection()->removeDocumentFromDocumentPool($this); |
1324
|
|
|
|
1325
|
|
|
return $this; |
1326
|
|
|
} |
1327
|
|
|
|
1328
|
|
|
/** |
1329
|
|
|
* |
1330
|
|
|
* @return \Sokil\Mongo\RevisionManager |
1331
|
|
|
*/ |
1332
|
|
|
public function getRevisionManager() |
1333
|
|
|
{ |
1334
|
|
|
if(!$this->revisionManager) { |
1335
|
|
|
$this->revisionManager = new RevisionManager($this); |
1336
|
|
|
} |
1337
|
|
|
|
1338
|
|
|
return $this->revisionManager; |
1339
|
|
|
} |
1340
|
|
|
|
1341
|
|
|
/** |
1342
|
|
|
* @deprecated since 1.13.0 use self::getRevisionManager()->getRevisions() |
1343
|
|
|
*/ |
1344
|
|
|
public function getRevisions($limit = null, $offset = null) |
1345
|
|
|
{ |
1346
|
|
|
return $this->getRevisionManager()->getRevisions($limit, $offset); |
1347
|
|
|
} |
1348
|
|
|
|
1349
|
|
|
/** |
1350
|
|
|
* @deprecated since 1.13.0 use self::getRevisionManager()->getRevision() |
1351
|
|
|
*/ |
1352
|
|
|
public function getRevision($id) |
1353
|
|
|
{ |
1354
|
|
|
return $this->getRevisionManager()->getRevision($id); |
1355
|
|
|
} |
1356
|
|
|
|
1357
|
|
|
/** |
1358
|
|
|
* @deprecated since 1.13.0 use self::getRevisionManager()->getRevisionsCount() |
1359
|
|
|
*/ |
1360
|
|
|
public function getRevisionsCount() |
1361
|
|
|
{ |
1362
|
|
|
return $this->getRevisionManager()->getRevisionsCount(); |
1363
|
|
|
} |
1364
|
|
|
|
1365
|
|
|
/** |
1366
|
|
|
* @deprecated since 1.13.0 use self::getRevisionManager()->clearRevisions() |
1367
|
|
|
*/ |
1368
|
|
|
public function clearRevisions() |
1369
|
|
|
{ |
1370
|
|
|
$this->getRevisionManager()->clearRevisions(); |
1371
|
|
|
return $this; |
1372
|
|
|
} |
1373
|
|
|
|
1374
|
|
|
} |
1375
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.