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 ( d990aa...8baddc )
by De
01:56
created

Document::addToSet()   D

Complexity

Conditions 10
Paths 10

Size

Total Lines 45
Code Lines 27

Duplication

Lines 15
Ratio 33.33 %

Importance

Changes 0
Metric Value
dl 15
loc 45
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 27
nc 10
nop 2

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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