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 ( c451b6...d37a73 )
by De
15:02
created

Document::delete()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 21
Code Lines 10

Duplication

Lines 3
Ratio 14.29 %

Importance

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