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 ( 6052ae...5ff50a )
by De
04:29
created

Document::createReference()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

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