GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Document::increment()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 11
rs 9.4285
cc 2
eloc 5
nc 2
nop 2
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], ...]
0 ignored issues
show
Unused Code Comprehensibility introduced by
48% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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();
0 ignored issues
show
Deprecated Code introduced by
The method Sokil\Mongo\Cursor::findAll() has been deprecated with message: since v.1.22.2. Use ::one instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
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)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (set() instead of push()). Are you sure this is correct? If so, you might want to change this to $this->set().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
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)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (set() instead of pushEach()). Are you sure this is correct? If so, you might want to change this to $this->set().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
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()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (set() instead of addToSet()). Are you sure this is correct? If so, you might want to change this to $this->set().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
1024
1025
        return $this;
1026
    }
1027
1028
    public function addToSetEach($fieldName, array $values)
0 ignored issues
show
Unused Code introduced by
The parameter $fieldName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $values is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (set() instead of increment()). Are you sure this is correct? If so, you might want to change this to $this->set().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1065
    {
1066
        parent::set($field, (int) $this->get($field) & $value);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (set() instead of bitwiceAnd()). Are you sure this is correct? If so, you might want to change this to $this->set().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
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)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1076
    {
1077
        parent::set($field, (int) $this->get($field) | $value);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (set() instead of bitwiceOr()). Are you sure this is correct? If so, you might want to change this to $this->set().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
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);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (set() instead of bitwiceXor()). Are you sure this is correct? If so, you might want to change this to $this->set().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
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