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.
Completed
Push — master ( f6151a...357aa0 )
by De
02:51
created

Document::belongsToCollection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/**
4
 * This file is part of the PHPMongo package.
5
 *
6
 * (c) Dmytro Sokil <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sokil\Mongo;
13
14
use Sokil\Mongo\Document\RelationManager;
15
use Sokil\Mongo\Document\RevisionManager;
16
use Sokil\Mongo\Document\InvalidDocumentException;
17
use Sokil\Mongo\Collection\Definition;
18
use Sokil\Mongo\Document\OptimisticLockFailureException;
19
use Symfony\Component\EventDispatcher\EventDispatcher;
20
use GeoJson\Geometry\Geometry;
21
22
/**
23
 * Instance of this class is a representation of one document from collection.
24
 *
25
 * @link https://github.com/sokil/php-mongo#document-schema Document schema
26
 * @link https://github.com/sokil/php-mongo#create-new-document Create new document
27
 * @link https://github.com/sokil/php-mongo#get-and-set-data-in-document get and set data
28
 * @link https://github.com/sokil/php-mongo#storing-document Saving document
29
 * @link https://github.com/sokil/php-mongo#document-validation Validation
30
 * @link https://github.com/sokil/php-mongo#deleting-collections-and-documents Deleting documents
31
 * @link https://github.com/sokil/php-mongo#events Event handlers
32
 * @link https://github.com/sokil/php-mongo#behaviors Behaviors
33
 * @link https://github.com/sokil/php-mongo#relations Relations
34
 *
35
 * @method \Sokil\Mongo\Document onAfterConstruct(callable $handler, int $priority = 0)
36
 * @method \Sokil\Mongo\Document onBeforeValidate(callable $handler, int $priority = 0)
37
 * @method \Sokil\Mongo\Document onAfterValidate(callable $handler, int $priority = 0)
38
 * @method \Sokil\Mongo\Document onValidateError(callable $handler, int $priority = 0)
39
 * @method \Sokil\Mongo\Document onBeforeInsert(callable $handler, int $priority = 0)
40
 * @method \Sokil\Mongo\Document onAfterInsert(callable $handler, int $priority = 0)
41
 * @method \Sokil\Mongo\Document onBeforeUpdate(callable $handler, int $priority = 0)
42
 * @method \Sokil\Mongo\Document onAfterUpdate(callable $handler, int $priority = 0)
43
 * @method \Sokil\Mongo\Document onBeforeSave(callable $handler, int $priority = 0)
44
 * @method \Sokil\Mongo\Document onAfterSave(callable $handler, int $priority = 0)
45
 * @method \Sokil\Mongo\Document onBeforeDelete(callable $handler, int $priority = 0)
46
 * @method \Sokil\Mongo\Document onAfterDelete(callable $handler, int $priority = 0)
47
 *
48
 * @author Dmytro Sokil <[email protected]>
49
 */
50
class Document extends Structure
51
{
52
    /**
53
     *
54
     * @var \Sokil\Mongo\Document\RelationManager
55
     */
56
    private $relationManager;
57
58
    const RELATION_HAS_ONE = 'HAS_ONE';
59
    const RELATION_BELONGS = 'BELONGS';
60
    const RELATION_HAS_MANY = 'HAS_MANY';
61
    const RELATION_MANY_MANY = 'MANY_MANY';
62
63
    /**
64
     *
65
     * @var \Sokil\Mongo\Document\RevisionManager
66
     */
67
    private $revisionManager;
68
69
    /**
70
     *
71
     * @var \Sokil\Mongo\Collection
72
     */
73
    private $collection;
74
75
    /**
76
     * @var \Symfony\Component\EventDispatcher\EventDispatcher Event Dispatcher instance
77
     */
78
    private $eventDispatcher;
79
80
    /**
81
     * @var \Sokil\Mongo\Operator Modification operator instance
82
     */
83
    private $operator;
84
85
    /**
86
     *
87
     * @var array list of defined behaviors
88
     */
89
    private $behaviors = array();
90
91
    /**
92
     *
93
     * @var array document options
94
     */
95
    private $options;
96
97
    /**
98
     * @param \Sokil\Mongo\Collection $collection instance of Mongo collection
99
     * @param array $data mongo document
100
     * @param array $options options of object initialization
101
     */
102
    public function __construct(Collection $collection, array $data = null, array $options = array())
103
    {
104
        // lisk to collection
105
        $this->collection = $collection;
106
107
        // configure document with options
108
        $this->options = $options;
109
110
        // init document
111
        $this->initDelegates();
112
113
        // execute before construct callable
114
        $this->beforeConstruct();
115
116
        // initialize with data
117
        parent::__construct($data, $this->getOption('stored'));
118
119
        // use versioning
120
        if($this->getOption('versioning')) {
121
            $this->getRevisionManager()->listen();
122
        }
123
124
        // execure after construct event handlers
125
        $this->eventDispatcher->dispatch('afterConstruct');
126
    }
127
128
    public function getOptions()
129
    {
130
        return $this->options;
131
    }
132
133
    public function getOption($name, $default = null)
134
    {
135
        return isset($this->options[$name]) ? $this->options[$name] : $default;
136
    }
137
138
    public function hasOption($name)
139
    {
140
        return isset($this->options[$name]);
141
    }
142
143
    /**
144
     * Event handler, called before running constructor.
145
     * May be overridden in child classes
146
     */
147
    public function beforeConstruct()
148
    {
149
150
    }
151
152
    /**
153
     * Get instance of collection
154
     * @return \Sokil\Mongo\Collection
155
     */
156
    public function getCollection()
157
    {
158
        return $this->collection;
159
    }
160
161
    /**
162
     * Reset all data passed to object in run-time, like events, behaviors,
163
     * data modifications, etc. to the state just after open or save document
164
     *
165
     * @return \Sokil\Mongo\Document
166
     */
167
    public function reset()
168
    {
169
        // reset structure
170
        parent::reset();
171
172
        // reset errors
173
        $this->clearErrors();
174
175
        // reset behaviors
176
        $this->clearBehaviors();
177
178
        // init delegates
179
        $this->initDelegates();
180
181
        return $this;
182
    }
183
184
    /**
185
     * Reload data from db and reset all unsaved data
186
     */
187
    public function refresh()
188
    {
189
        $data = $this->collection
190
            ->getMongoCollection()
191
            ->findOne(array(
192
                '_id' => $this->getId()
193
            ));
194
195
        $this->replace($data);
196
197
        $this->operator->reset();
198
199
        return $this;
200
    }
201
202
    /**
203
     * Initialise relative classes
204
     */
205
    private function initDelegates()
206
    {
207
        // start event dispatching
208
        $this->eventDispatcher = new EventDispatcher;
209
        
210
        // create operator
211
        $this->operator = $this->getCollection()->operator();
212
213
        // attach behaviors
214
        $this->attachBehaviors($this->behaviors());
215
        if($this->hasOption('behaviors')) {
216
            $this->attachBehaviors($this->getOption('behaviors'));
217
        }
218
    }
219
220
    public function __toString()
221
    {
222
        return (string) $this->getId();
223
    }
224
225
    public function __call($name, $arguments)
226
    {
227
        // behaviors
228
        foreach ($this->behaviors as $behavior) {
229
            if (!method_exists($behavior, $name)) {
230
                continue;
231
            }
232
233
            return call_user_func_array(array($behavior, $name), $arguments);
234
        }
235
236
        // adding event
237
        if('on' === substr($name, 0, 2)) {
238
            // prepend event name to function args
239
            $addListenerArguments = $arguments;
240
            array_unshift($addListenerArguments, lcfirst(substr($name, 2)));
241
            // add listener
242
            call_user_func_array(
243
                array($this->eventDispatcher, 'addListener'),
244
                $addListenerArguments
245
            );
246
            
247
            return $this;
248
        }
249
250
        // getter
251
        if ('get' === strtolower(substr($name, 0, 3))) {
252
            return $this->get(lcfirst(substr($name, 3)));
253
        }
254
255
        // setter
256
        if ('set' === strtolower(substr($name, 0, 3)) && isset($arguments[0])) {
257
            return $this->set(lcfirst(substr($name, 3)), $arguments[0]);
258
        }
259
260
        throw new Exception('Document has no method "' . $name . '"');
261
    }
262
263
    public function __get($name)
264
    {
265
        if ($this->getRelationManager()->isRelationExists($name)) {
266
            // resolve relation
267
            return $this->getRelationManager()->getRelated($name);
268
        } else {
269
            // get document parameter
270
            return parent::__get($name);
271
        }
272
    }
273
274
    /**
275
     * Set geo data as GeoJson object
276
     *
277
     * Requires MongoDB version 2.4 or above with 2dsparse index version 1
278
     * to use Point, LineString and Polygon.
279
     *
280
     * Requires MongoDB version 2.6 or above with 2dsparse index version 2
281
     * to use MultiPoint, MultiLineString, MultiPolygon and GeometryCollection.
282
     *
283
     * @link http://geojson.org/
284
     * @param string $field
285
     * @param \GeoJson\Geometry\Geometry $geometry
286
     * @return \Sokil\Mongo\Document
287
     */
288
    public function setGeometry($field, Geometry $geometry)
289
    {
290
        return $this->set($field, $geometry);
291
    }
292
293
    /**
294
     * Set point as longitude and latitude
295
     *
296
     * Requires MongoDB version 2.4 or above with 2dsparse index version 1
297
     * to use Point, LineString and Polygon.
298
     *
299
     * @link http://docs.mongodb.org/manual/core/2dsphere/#point
300
     * @param string $field
301
     * @param float $longitude
302
     * @param float $latitude
303
     * @return \Sokil\Mongo\Document
304
     */
305
    public function setPoint($field, $longitude, $latitude)
306
    {
307
        return $this->setGeometry(
308
            $field,
309
            new \GeoJson\Geometry\Point(array(
310
                $longitude,
311
                $latitude
312
            ))
313
        );
314
    }
315
316
    /**
317
     * Set point as longitude and latitude in legacy format
318
     *
319
     * May be used 2d index
320
     *
321
     * @link http://docs.mongodb.org/manual/core/2d/#geospatial-indexes-store-grid-coordinates
322
     * @param string $field
323
     * @param float $longitude
324
     * @param float $latitude
325
     * @return \Sokil\Mongo\Document
326
     */
327
    public function setLegacyPoint($field, $longitude, $latitude)
328
    {
329
        return $this->set(
330
            $field,
331
            array($longitude, $latitude)
332
        );
333
    }
334
335
    /**
336
     * Set line string as array of points
337
     *
338
     * Requires MongoDB version 2.4 or above with 2dsparse index version 1
339
     * to use Point, LineString and Polygon.
340
     *
341
     * @link http://docs.mongodb.org/manual/core/2dsphere/#linestring
342
     * @param string $field
343
     * @param array $pointArray array of points
344
     * @return \Sokil\Mongo\Document
345
     */
346
    public function setLineString($field, array $pointArray)
347
    {
348
        return $this->setGeometry(
349
            $field,
350
            new \GeoJson\Geometry\LineString($pointArray)
351
        );
352
    }
353
354
    /**
355
     * Set polygon as array of line rings.
356
     *
357
     * Line ring is closed line string (first and last point same).
358
     * Line string is array of points.
359
     *
360
     * Requires MongoDB version 2.4 or above with 2dsparse index version 1
361
     * to use Point, LineString and Polygon.
362
     *
363
     * @link http://docs.mongodb.org/manual/core/2dsphere/#polygon
364
     * @param string $field
365
     * @param array $lineRingsArray array of line rings
366
     * @return \Sokil\Mongo\Document
367
     */
368
    public function setPolygon($field, array $lineRingsArray)
369
    {
370
        return $this->setGeometry(
371
            $field,
372
            new \GeoJson\Geometry\Polygon($lineRingsArray)
373
        );
374
    }
375
376
    /**
377
     * Set multi point as array of points
378
     *
379
     * Requires MongoDB version 2.6 or above with 2dsparse index version 2
380
     * to use MultiPoint, MultiLineString, MultiPolygon and GeometryCollection.
381
     *
382
     * @link http://docs.mongodb.org/manual/core/2dsphere/#multipoint
383
     * @param string $field
384
     * @param array $pointArray array of point arrays
385
     * @return \Sokil\Mongo\Document
386
     */
387
    public function setMultiPoint($field, $pointArray)
388
    {
389
        return $this->setGeometry(
390
            $field,
391
            new \GeoJson\Geometry\MultiPoint($pointArray)
392
        );
393
    }
394
395
    /**
396
     * Set multi line string as array of line strings
397
     *
398
     * Requires MongoDB version 2.6 or above with 2dsparse index version 2
399
     * to use MultiPoint, MultiLineString, MultiPolygon and GeometryCollection.
400
     *
401
     * http://docs.mongodb.org/manual/core/2dsphere/#multilinestring
402
     * @param string $field
403
     * @param array $lineStringArray array of line strings
404
     * @return \Sokil\Mongo\Document
405
     */
406
    public function setMultiLineString($field, $lineStringArray)
407
    {
408
        return $this->setGeometry(
409
            $field,
410
            new \GeoJson\Geometry\MultiLineString($lineStringArray)
411
        );
412
    }
413
414
    /**
415
     * Set multy polygon as array of polygons.
416
     *
417
     * Polygon is array of line rings.
418
     * Line ring is closed line string (first and last point same).
419
     * Line string is array of points.
420
     *
421
     * Requires MongoDB version 2.6 or above with 2dsparse index version 2
422
     * to use MultiPoint, MultiLineString, MultiPolygon and GeometryCollection.
423
     *
424
     * @link http://docs.mongodb.org/manual/core/2dsphere/#multipolygon
425
     * @param string $field
426
     * @param array $polygonsArray array of polygons
427
     * @return \Sokil\Mongo\Document
428
     */
429
    public function setMultyPolygon($field, array $polygonsArray)
430
    {
431
        return $this->setGeometry(
432
            $field,
433
            new \GeoJson\Geometry\MultiPolygon($polygonsArray)
434
        );
435
    }
436
437
    /**
438
     * Set collection of different geometries
439
     *
440
     * Requires MongoDB version 2.6 or above with 2dsparse index version 2
441
     * to use MultiPoint, MultiLineString, MultiPolygon and GeometryCollection.
442
     *
443
     * @link http://docs.mongodb.org/manual/core/2dsphere/#geometrycollection
444
     * @param string $field
445
     * @param array $geometryCollection
446
     * @return \Sokil\Mongo\Document
447
     */
448
    public function setGeometryCollection($field, array $geometryCollection)
449
    {
450
        return $this->setGeometry(
451
            $field,
452
            new \GeoJson\Geometry\GeometryCollection($geometryCollection)
453
        );
454
    }
455
456
    /**
457
     * Override in child class to define relations
458
     * @return array relation description
459
     */
460
    protected function relations()
461
    {
462
        // [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...
463
        return array();
464
    }
465
466
    /**
467
     * Relation definition through mapping is more prior to defined in class
468
     * @return array definition of relations
469
     */
470
    public function getRelationDefinition()
471
    {
472
        $relations = $this->getOption('relations');
473
        if(!is_array($relations)) {
474
            return $this->relations();
475
        }
476
477
        return $relations + $this->relations();
478
    }
479
480
    /**
481
     *
482
     * @return \Sokil\Mongo\Document\RelationManager
483
     */
484
    private function getRelationManager()
485
    {
486
        if($this->relationManager) {
487
            return $this->relationManager;
488
        }
489
490
        $this->relationManager = new RelationManager($this);
491
492
        return $this->relationManager;
493
    }
494
495
    /**
496
     * Get related documents
497
     * @param string $relationName
498
     * @return array|\Sokil\Mongo\Document related document or array of documents
499
     */
500
    public function getRelated($relationName)
501
    {
502
        return $this->getRelationManager()->getRelated($relationName);
503
    }
504
505
    public function addRelation($relationName, Document $document)
506
    {
507
        $this->getRelationManager()->addRelation($relationName, $document);
508
509
        return $this;
510
    }
511
512
    public function removeRelation($relationName, Document $document = null)
513
    {
514
        $this->getRelationManager()->removeRelation($relationName, $document);
515
516
        return $this;
517
    }
518
519
    /**
520
     * Manually trigger defined events
521
     * @param string $eventName event name
522
     * @return \Sokil\Mongo\Event
523
     */
524
    public function triggerEvent($eventName, Event $event = null)
525
    {
526
        if(!$event) {
527
            $event = new Event;
528
        }
529
530
        $event->setTarget($this);
531
532
        return $this->eventDispatcher->dispatch($eventName, $event);
533
    }
534
535
    /**
536
     * Attach event handler
537
     * @param string $event event name
538
     * @param callable|array|string $handler event handler
539
     * @return \Sokil\Mongo\Document
540
     */
541
    public function attachEvent($event, $handler, $priority = 0)
542
    {
543
        $this->eventDispatcher->addListener($event, $handler, $priority);
544
        return $this;
545
    }
546
547
    /**
548
     * Check if event attached
549
     *
550
     * @param string $event event name
551
     * @return bool
552
     */
553
    public function hasEvent($event)
554
    {
555
        return $this->eventDispatcher->hasListeners($event);
556
    }
557
558
    public function getId()
559
    {
560
        return $this->get('_id');
561
    }
562
563
    /**
564
     * Used to define id of stored document. This id must be already present in db
565
     *
566
     * @param \MongoId|string $id id of document
567
     * @return Document
568
     */
569 View Code Duplication
    public function defineId($id)
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...
570
    {
571
        if (!($id instanceof \MongoId)) {
572
            try {
573
                $id = new \MongoId($id);
574
            } catch (\MongoException $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
575
        }
576
577
        $this->mergeUnmodified(array('_id' => $id));
578
579
        return $this;
580
    }
581
582
    /**
583
     * Used to define id of not stored document.
584
     *
585
     * @param \MongoId|string $id id of document
586
     * @return Document
587
     */
588 View Code Duplication
    public function setId($id)
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...
589
    {
590
        if (!($id instanceof \MongoId)) {
591
            try {
592
                $id = new \MongoId($id);
593
            } catch (\MongoException $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
594
        }
595
596
        return $this->set('_id', $id);
597
    }
598
599
    public function isStored()
600
    {
601
        return $this->get('_id') && !$this->isModified('_id');
602
    }
603
604
    /**
605
     *
606
     * @throws \Sokil\Mongo\Document\InvalidDocumentException
607
     * @return \Sokil\Mongo\Document
608
     */
609
    public function validate()
610
    {
611
        if($this->triggerEvent('beforeValidate')->isCancelled()) {
612
            return $this;
613
        }
614
615
        if (!$this->isValid()) {
616
            $exception = new InvalidDocumentException('Document not valid');
617
            $exception->setDocument($this);
618
619
            $this->triggerEvent('validateError');
620
621
            throw $exception;
622
        }
623
624
        $this->triggerEvent('afterValidate');
625
626
        return $this;
627
    }
628
629
    public function behaviors()
630
    {
631
        return array();
632
    }
633
634
    public function attachBehaviors(array $behaviors)
635
    {
636
        foreach ($behaviors as $name => $behavior) {
637
            $this->attachBehavior($name, $behavior);
638
        }
639
640
        return $this;
641
    }
642
643
    /**
644
     *
645
     * @param string $name unique name of attached behavior
646
     * @param string|array|\Sokil\Mongo\Behavior $behavior Behavior instance or behavior definition
647
     * @return \Sokil\Mongo\Document
648
     * @throws Exception
649
     */
650
    public function attachBehavior($name, $behavior)
651
    {
652
        if(is_string($behavior)) {
653
            // behavior defined as string
654
            $className = $behavior;
655
            $behavior = new $className();
656
        } elseif(is_array($behavior)) {
657
            // behavior defined as array
658
            if (empty($behavior['class'])) {
659
                throw new Exception('Behavior class not specified');
660
            }
661
            $className = $behavior['class'];
662
            unset($behavior['class']);
663
            $behavior = new $className($behavior);
664
        } elseif (!($behavior instanceof Behavior)) {
665
            // behavior bust be Behavior instance, but something else found
666
            throw new Exception('Wrong behavior specified with name ' . $name);
667
        }
668
669
        $behavior->setOwner($this);
670
671
        $this->behaviors[$name] = $behavior;
672
673
        return $this;
674
    }
675
676
    public function clearBehaviors()
677
    {
678
        $this->behaviors = array();
679
        return $this;
680
    }
681
682
    public function getOperator()
683
    {
684
        return $this->operator;
685
    }
686
687
    public function isModificationOperatorDefined()
688
    {
689
        return $this->operator->isDefined();
690
    }
691
692
    /**
693
     * Update value in local cache and in DB
694
     *
695
     * @param string $fieldName point-delimited field name
696
     * @param mixed $value value to store
697
     * @return \Sokil\Mongo\Document
698
     */
699
    public function set($fieldName, $value)
700
    {
701
        parent::set($fieldName, $value);
702
703
        // if document saved - save through update
704
        if ($this->getId()) {
705
            $this->operator->set($fieldName, $value);
706
        }
707
708
        return $this;
709
    }
710
711
    /**
712
     * Remove field
713
     * 
714
     * @param string $fieldName field name
715
     * @return \Sokil\Mongo\Document
716
     */
717
    public function unsetField($fieldName)
718
    {
719
        if (!$this->has($fieldName)) {
720
            return $this;
721
        }
722
723
        parent::unsetField($fieldName);
724
725
        if ($this->getId()) {
726
            $this->operator->unsetField($fieldName);
727
        }
728
729
        return $this;
730
    }
731
732
    public function __unset($fieldName)
733
    {
734
        $this->unsetField($fieldName);
735
    }
736
737
    /**
738
     * Get reference to document
739
     *
740
     * @throws Exception
741
     * @return array
742
     */
743
    public function createReference()
744
    {
745
        $documentId = $this->getId();
746
        if (null === $documentId) {
747
            throw new Exception('Document must be stored to get DBRef');
748
        }
749
750
        return $this
751
            ->getCollection()
752
            ->getMongoCollection()
753
            ->createDBRef($documentId);
754
    }
755
756
    /**
757
     * Store DBRef to specified field
758
     *
759
     * @param $name
760
     * @param Document $document
761
     * @return Document
762
     */
763
    public function setReference($name, Document $document)
764
    {
765
        return $this->set(
766
            $name,
767
            $document->createReference()
768
        );
769
    }
770
771
    /**
772
     * Get document by reference
773
     *
774
     * @param string    $name   name of field where reference stored
775
     * @return null|Document
776
     */
777
    public function getReferencedDocument($name)
778
    {
779
        $reference = $this->get($name);
780
        if (null === $reference) {
781
            return null;
782
        }
783
784
        return $this->collection
785
            ->getDatabase()
786
            ->getDocumentByReference($reference);
787
    }
788
789
    /**
790
     * Push reference to list
791
     *
792
     * @param string $name
793
     * @param Document $document
794
     * @return Document
795
     */
796
    public function pushReference($name, Document $document)
797
    {
798
        return $this->push(
799
            $name,
800
            $document->createReference()
801
        );
802
    }
803
804
    /**
805
     * Get document by reference
806
     *
807
     * @param string    $name   name of field where reference stored
808
     * @return null|Document
809
     */
810
    public function getReferencedDocumentList($name)
811
    {
812
        $referenceList = $this->get($name);
813
        if (null === $referenceList) {
814
            return null;
815
        }
816
817
        if (!isset($referenceList[0])) {
818
            throw new Exception('List of references not found');
819
        }
820
821
        // build list of referenced collections and ids
822
        $documentIdList = array();
823
        foreach ($referenceList as $reference) {
824
            if (empty($reference['$ref']) || empty($reference['$id'])) {
825
                throw new Exception(sprintf(
826
                    'Iinvalid reference in list for document %s in field %s',
827
                    $this->getId(),
828
                    $name
829
                ));
830
            }
831
832
            $documentIdList[$reference['$ref']][] = $reference['$id'];
833
        }
834
835
        // get list
836
        $documentList = array();
837
        $database = $this->collection->getDatabase();
838
        foreach ($documentIdList as $collectionName => $documentIdList) {
839
            $documentList += $database->getCollection($collectionName)->find()->byIdList($documentIdList)->findAll();
0 ignored issues
show
Bug introduced by
The method byIdList does only exist in Sokil\Mongo\Cursor, but not in Sokil\Mongo\Expression.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
840
        }
841
842
        return $documentList;
843
    }
844
845
    /**
846
     * @param array $data
847
     * @return Document
848
     */
849
    public function merge(array $data)
850
    {
851
        if ($this->isStored()) {
852
            foreach ($data as $fieldName => $value) {
853
                $this->set($fieldName, $value);
854
            }
855
        } else {
856
            parent::merge($data);
857
        }
858
859
        return $this;
860
    }
861
862
    /**
863
     * If field not exist - set value.
864
     * If field exists and is not array - convert to array and append
865
     * If field -s array - append
866
     *
867
     * @param type $selector
868
     * @param type $value
869
     * @return \Sokil\Mongo\Structure
870
     */
871
    public function append($selector, $value)
872
    {
873
        parent::append($selector, $value);
874
875
        // if document saved - save through update
876
        if ($this->getId()) {
877
            $this->operator->set($selector, $this->get($selector));
878
        }
879
880
        return $this;
881
    }
882
883
    /**
884
     * Push argument as single element to field value
885
     *
886
     * @param string $fieldName
887
     * @param mixed $value
888
     * @return \Sokil\Mongo\Document
889
      */
890
    public function push($fieldName, $value)
891
    {
892
        $oldValue = $this->get($fieldName);
893
894
        $value = Structure::prepareToStore($value);
895
896
        // field not exists
897
        if (!$oldValue) {
898
            if ($this->getId()) {
899
                $this->operator->push($fieldName, $value);
900
            }
901
            $value = array($value);
902
        } // field already exist and has single value
903 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...
904
            $value = array_merge((array) $oldValue, array($value));
905
            if ($this->getId()) {
906
                $this->operator->set($fieldName, $value);
907
            }
908
        } // field exists and is array
909
        else {
910
            if ($this->getId()) {
911
                // check if array because previous $set operation on single value was executed
912
                $setValue = $this->operator->get('$set', $fieldName);
913
                if ($setValue) {
914
                    $setValue[] = $value;
915
                    $this->operator->set($fieldName, $setValue);
916
                } else {
917
                    $this->operator->push($fieldName, $value);
918
                }
919
            }
920
            $value = array_merge($oldValue, array($value));
921
        }
922
923
        // update local data
924
        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...
925
926
        return $this;
927
    }
928
929
    /**
930
     * Push each element of argument's array as single element to field value
931
     *
932
     * @param string $fieldName
933
     * @param array $values
934
     * @return \Sokil\Mongo\Document
935
     */
936
    public function pushEach($fieldName, array $values)
937
    {
938
        $oldValue = $this->get($fieldName);
939
940
        if ($this->getId()) {
941
            if (!$oldValue) {
942
                $this->operator->pushEach($fieldName, $values);
943 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...
944
                $values = array_merge((array) $oldValue, $values);
945
                $this->operator->set($fieldName, $values);
946
            } else {
947
                $this->operator->pushEach($fieldName, $values);
948
                $values = array_merge($oldValue, $values);
949
            }
950
        } else {
951
            if ($oldValue) {
952
                $values = array_merge((array) $oldValue, $values);
953
            }
954
        }
955
956
        // update local data
957
        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...
958
959
        return $this;
960
    }
961
962
    public function addToSet($fieldName, $value)
963
    {
964
        $oldValues = $this->get($fieldName);
965
        if (!$oldValues) {
966
            $newValue = array($value);
967
            $this->operator->addToSet($fieldName, $value);
968
        } elseif (!is_array($value)) {
969
            if ($oldValues === $value) {
970
                return $this;
971
            }
972
            $newValue = array($oldValues, $value);
973
            $this->operator->set($fieldName, $newValue);
974
        } else {
975
            if (in_array($value, $oldValues)) {
976
                return $this;
977
            }
978
            $newValue = array_merge($oldValues, array($value));
979
            $this->operator->addToSet($fieldName, $value);
980
        }
981
982
        parent::set($fieldName, $newValue);
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...
983
984
        return $this;
985
    }
986
987
    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...
988
    {
989
        throw new \RuntimeException('Not implemented');
990
    }
991
992
    /**
993
     * Removes from an existing array all instances of a value or
994
     * values that match a specified query
995
     *
996
     * @param integer|string|array|\Sokil\Mongo\Expression|callable $expression
997
     * @param mixed|\Sokil\Mongo\Expression|callable $value
998
     * @return \Sokil\Mongo\Document
999
     */
1000
    public function pull($expression, $value = null)
1001
    {
1002
        $this->operator->pull($expression, $value);
1003
        return $this;
1004
    }
1005
1006
    public function increment($fieldName, $value = 1)
1007
    {
1008
        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...
1009
1010
        if ($this->getId()) {
1011
            $this->operator->increment($fieldName, $value);
1012
        }
1013
1014
1015
        return $this;
1016
    }
1017
1018
    public function decrement($fieldName, $value = 1)
1019
    {
1020
        return $this->increment($fieldName, -1 * $value);
1021
    }
1022
1023 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...
1024
    {
1025
        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...
1026
1027
        if ($this->getId()) {
1028
            $this->operator->bitwiceAnd($field, $value);
1029
        }
1030
1031
        return $this;
1032
    }
1033
1034 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...
1035
    {
1036
        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...
1037
1038
        if ($this->getId()) {
1039
            $this->operator->bitwiceOr($field, $value);
1040
        }
1041
1042
        return $this;
1043
    }
1044
1045
    public function bitwiceXor($field, $value)
1046
    {
1047
        $oldFieldValue = (int) $this->get($field);
1048
        $newValue = $oldFieldValue ^ $value;
1049
1050
        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...
1051
1052
        if ($this->getId()) {
1053
            if(version_compare($this->getCollection()->getDatabase()->getClient()->getDbVersion(), '2.6', '>=')) {
1054
                $this->operator->bitwiceXor($field, $value);
1055
            } else {
1056
                $this->operator->set($field, $newValue);
1057
            }
1058
        }
1059
1060
        return $this;
1061
    }
1062
1063
    public function save($validate = true)
1064
    {
1065
        // save document
1066
        // if document already in db and not modified - skip this method
1067
        if (!$this->isSaveRequired()) {
1068
            return $this;
1069
        }
1070
1071
        if ($validate) {
1072
            $this->validate();
1073
        }
1074
1075
        // handle beforeSave event
1076
        if($this->triggerEvent('beforeSave')->isCancelled()) {
1077
            return $this;
1078
        }
1079
        if ($this->isStored()) {
1080
            if($this->triggerEvent('beforeUpdate')->isCancelled()) {
1081
                return $this;
1082
            }
1083
1084
            // locking
1085
            $query = array('_id' => $this->getId());
1086
            if ($this->getOption('lock') === Definition::LOCK_OPTIMISTIC) {
1087
                $query['__version__'] = $this->get('__version__');
1088
                $this->getOperator()->increment('__version__');
1089
            }
1090
1091
            // update
1092
            $status = $this->collection
1093
                ->getMongoCollection()
1094
                ->update(
1095
                    $query,
1096
                    $this->getOperator()->toArray()
1097
                );
1098
1099
            // check update status
1100 View Code Duplication
            if ($status['ok'] != 1) {
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...
1101
                throw new \Sokil\Mongo\Exception(sprintf(
1102
                    'Update error: %s: %s',
1103
                    $status['err'],
1104
                    $status['errmsg']
1105
                ));
1106
            }
1107
1108
            // check if document modified due to specified lock
1109
            if ($this->getOption('lock') === Definition::LOCK_OPTIMISTIC) {
1110
                if($status['n'] === 0) {
1111
                    throw new OptimisticLockFailureException;
1112
                }
1113
            }
1114
1115
            if ($this->getOperator()->isReloadRequired()) {
1116
                $this->refresh();
1117
            } else {
1118
                $this->getOperator()->reset();
1119
            }
1120
1121
            $this->triggerEvent('afterUpdate');
1122
        } else {
1123
            if($this->triggerEvent('beforeInsert')->isCancelled()) {
1124
                return $this;
1125
            }
1126
1127
            $document = $this->toArray();
1128
1129
            $this
1130
                ->collection
1131
                ->getMongoCollection()
1132
                ->insert($document);
1133
            
1134
            // set id
1135
            $this->defineId($document['_id']);
1136
1137
            // after insert event
1138
            $this->triggerEvent('afterInsert');
1139
        }
1140
1141
        // handle afterSave event
1142
        $this->triggerEvent('afterSave');
1143
1144
        $this->apply();
1145
        
1146
        return $this;
1147
    }
1148
1149
    public function isSaveRequired()
1150
    {
1151
        return !$this->isStored() || $this->isModified() || $this->isModificationOperatorDefined();
1152
    }
1153
1154
    public function delete()
1155
    {
1156
        if ($this->triggerEvent('beforeDelete')->isCancelled()) {
1157
            return $this;
1158
        }
1159
1160
        $status = $this->collection->getMongoCollection()->remove(array(
1161
            '_id'   => $this->getId(),
1162
        ));
1163
1164 View Code Duplication
        if(true !== $status && $status['ok'] != 1) {
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...
1165
            throw new \Sokil\Mongo\Exception(sprintf('Delete document error: %s', $status['err']));
1166
        }
1167
1168
        $this->triggerEvent('afterDelete');
1169
1170
        // drop from document's pool
1171
        $this->getCollection()->removeDocumentFromDocumentPool($this);
1172
1173
        return $this;
1174
    }
1175
1176
    /**
1177
     *
1178
     * @return \Sokil\Mongo\RevisionManager
1179
     */
1180
    public function getRevisionManager()
1181
    {
1182
        if(!$this->revisionManager) {
1183
            $this->revisionManager = new RevisionManager($this);
1184
        }
1185
1186
        return $this->revisionManager;
1187
    }
1188
1189
    /**
1190
     * @deprecated since 1.13.0 use self::getRevisionManager()->getRevisions()
1191
     */
1192
    public function getRevisions($limit = null, $offset = null)
1193
    {
1194
        return $this->getRevisionManager()->getRevisions($limit, $offset);
1195
    }
1196
1197
    /**
1198
     * @deprecated since 1.13.0 use self::getRevisionManager()->getRevision()
1199
     */
1200
    public function getRevision($id)
1201
    {
1202
        return $this->getRevisionManager()->getRevision($id);
1203
    }
1204
1205
    /**
1206
     * @deprecated since 1.13.0 use self::getRevisionManager()->getRevisionsCount()
1207
     */
1208
    public function getRevisionsCount()
1209
    {
1210
        return $this->getRevisionManager()->getRevisionsCount();
1211
    }
1212
1213
    /**
1214
     * @deprecated since 1.13.0 use self::getRevisionManager()->clearRevisions()
1215
     */
1216
    public function clearRevisions()
1217
    {
1218
        $this->getRevisionManager()->clearRevisions();
1219
        return $this;
1220
    }
1221
1222
}
1223