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