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 ( bd8707...a7f508 )
by De
07:21
created

Document::getValidatorClassNameByRuleName()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 17
rs 9.2
cc 4
eloc 10
nc 6
nop 1

2 Methods

Rating   Name   Duplication   Size   Complexity  
A Document::getOperator() 0 4 1
A Document::isModificationOperatorDefined() 0 4 1
1
<?php
2
3
/**
4
 * This file is part of the PHPMongo package.
5
 *
6
 * (c) Dmytro Sokil <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sokil\Mongo;
13
14
use Sokil\Mongo\Document\RelationManager;
15
use Sokil\Mongo\Document\RevisionManager;
16
use Sokil\Mongo\Document\InvalidDocumentException;
17
use Sokil\Mongo\Collection\Definition;
18
use Sokil\Mongo\Document\OptimisticLockFailureException;
19
use Symfony\Component\EventDispatcher\EventDispatcher;
20
use GeoJson\Geometry\Geometry;
21
22
/**
23
 * Instance of this class is a representation of one document from collection.
24
 *
25
 * @link https://github.com/sokil/php-mongo#document-schema Document schema
26
 * @link https://github.com/sokil/php-mongo#create-new-document Create new document
27
 * @link https://github.com/sokil/php-mongo#get-and-set-data-in-document get and set data
28
 * @link https://github.com/sokil/php-mongo#storing-document Saving document
29
 * @link https://github.com/sokil/php-mongo#document-validation Validation
30
 * @link https://github.com/sokil/php-mongo#deleting-collections-and-documents Deleting documents
31
 * @link https://github.com/sokil/php-mongo#events Event handlers
32
 * @link https://github.com/sokil/php-mongo#behaviors Behaviors
33
 * @link https://github.com/sokil/php-mongo#relations Relations
34
 *
35
 * @method \Sokil\Mongo\Document onAfterConstruct(callable $handler, int $priority = 0)
36
 * @method \Sokil\Mongo\Document onBeforeValidate(callable $handler, int $priority = 0)
37
 * @method \Sokil\Mongo\Document onAfterValidate(callable $handler, int $priority = 0)
38
 * @method \Sokil\Mongo\Document onValidateError(callable $handler, int $priority = 0)
39
 * @method \Sokil\Mongo\Document onBeforeInsert(callable $handler, int $priority = 0)
40
 * @method \Sokil\Mongo\Document onAfterInsert(callable $handler, int $priority = 0)
41
 * @method \Sokil\Mongo\Document onBeforeUpdate(callable $handler, int $priority = 0)
42
 * @method \Sokil\Mongo\Document onAfterUpdate(callable $handler, int $priority = 0)
43
 * @method \Sokil\Mongo\Document onBeforeSave(callable $handler, int $priority = 0)
44
 * @method \Sokil\Mongo\Document onAfterSave(callable $handler, int $priority = 0)
45
 * @method \Sokil\Mongo\Document onBeforeDelete(callable $handler, int $priority = 0)
46
 * @method \Sokil\Mongo\Document onAfterDelete(callable $handler, int $priority = 0)
47
 *
48
 * @author Dmytro Sokil <[email protected]>
49
 */
50
class Document extends Structure
51
{
52
    /**
53
     * @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
    public function merge(array $data)
773
    {
774
        if ($this->isStored()) {
775
            foreach ($data as $fieldName => $value) {
776
                $this->set($fieldName, $value);
777
            }
778
        } else {
779
            parent::merge($data);
780
        }
781
782
        return $this;
783
    }
784
785
    /**
786
     * If field not exist - set value.
787
     * If field exists and is not array - convert to array and append
788
     * If field -s array - append
789
     *
790
     * @param type $selector
791
     * @param type $value
792
     * @return \Sokil\Mongo\Structure
793
     */
794
    public function append($selector, $value)
795
    {
796
        parent::append($selector, $value);
797
798
        // if document saved - save through update
799
        if ($this->getId()) {
800
            $this->operator->set($selector, $this->get($selector));
801
        }
802
803
        return $this;
804
    }
805
806
    /**
807
     * Push argument as single element to field value
808
     *
809
     * @param string $fieldName
810
     * @param mixed $value
811
     * @return \Sokil\Mongo\Document
812
      */
813
    public function push($fieldName, $value)
814
    {
815
        $oldValue = $this->get($fieldName);
816
817
        $value = Structure::prepareToStore($value);
818
819
        // field not exists
820
        if (!$oldValue) {
821
            if ($this->getId()) {
822
                $this->operator->push($fieldName, $value);
823
            }
824
            $value = array($value);
825
        } // field already exist and has single value
826 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...
827
            $value = array_merge((array) $oldValue, array($value));
828
            if ($this->getId()) {
829
                $this->operator->set($fieldName, $value);
830
            }
831
        } // field exists and is array
832
        else {
833
            if ($this->getId()) {
834
                // check if array because previous $set operation on single value was executed
835
                $setValue = $this->operator->get('$set', $fieldName);
836
                if ($setValue) {
837
                    $setValue[] = $value;
838
                    $this->operator->set($fieldName, $setValue);
839
                } else {
840
                    $this->operator->push($fieldName, $value);
841
                }
842
            }
843
            $value = array_merge($oldValue, array($value));
844
        }
845
846
        // update local data
847
        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...
848
849
        return $this;
850
    }
851
852
    /**
853
     * Push each element of argument's array as single element to field value
854
     *
855
     * @param string $fieldName
856
     * @param array $values
857
     * @return \Sokil\Mongo\Document
858
     */
859
    public function pushEach($fieldName, array $values)
860
    {
861
        $oldValue = $this->get($fieldName);
862
863
        if ($this->getId()) {
864
            if (!$oldValue) {
865
                $this->operator->pushEach($fieldName, $values);
866 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...
867
                $values = array_merge((array) $oldValue, $values);
868
                $this->operator->set($fieldName, $values);
869
            } else {
870
                $this->operator->pushEach($fieldName, $values);
871
                $values = array_merge($oldValue, $values);
872
            }
873
        } else {
874
            if ($oldValue) {
875
                $values = array_merge((array) $oldValue, $values);
876
            }
877
        }
878
879
        // update local data
880
        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...
881
882
        return $this;
883
    }
884
885
    public function addToSet($fieldName, $value)
886
    {
887
        $oldValues = $this->get($fieldName);
888
        if (!$oldValues) {
889
            $newValue = array($value);
890
            $this->operator->addToSet($fieldName, $value);
891
        } elseif (!is_array($value)) {
892
            if ($oldValues === $value) {
893
                return $this;
894
            }
895
            $newValue = array($oldValues, $value);
896
            $this->operator->set($fieldName, $newValue);
897
        } else {
898
            if (in_array($value, $oldValues)) {
899
                return $this;
900
            }
901
            $newValue = array_merge($oldValues, array($value));
902
            $this->operator->addToSet($fieldName, $value);
903
        }
904
905
        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...
906
907
        return $this;
908
    }
909
910
    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...
911
    {
912
        throw new \RuntimeException('Not implemented');
913
    }
914
915
    /**
916
     * Removes from an existing array all instances of a value or
917
     * values that match a specified query
918
     *
919
     * @param integer|string|array|\Sokil\Mongo\Expression|callable $expression
920
     * @param mixed|\Sokil\Mongo\Expression|callable $value
921
     * @return \Sokil\Mongo\Document
922
     */
923
    public function pull($expression, $value = null)
924
    {
925
        $this->operator->pull($expression, $value);
926
        return $this;
927
    }
928
929
    public function increment($fieldName, $value = 1)
930
    {
931
        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...
932
933
        if ($this->getId()) {
934
            $this->operator->increment($fieldName, $value);
935
        }
936
937
938
        return $this;
939
    }
940
941
    public function decrement($fieldName, $value = 1)
942
    {
943
        return $this->increment($fieldName, -1 * $value);
944
    }
945
946 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...
947
    {
948
        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...
949
950
        if ($this->getId()) {
951
            $this->operator->bitwiceAnd($field, $value);
952
        }
953
954
        return $this;
955
    }
956
957 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...
958
    {
959
        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...
960
961
        if ($this->getId()) {
962
            $this->operator->bitwiceOr($field, $value);
963
        }
964
965
        return $this;
966
    }
967
968
    public function bitwiceXor($field, $value)
969
    {
970
        $oldFieldValue = (int) $this->get($field);
971
        $newValue = $oldFieldValue ^ $value;
972
973
        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...
974
975
        if ($this->getId()) {
976
            if(version_compare($this->getCollection()->getDatabase()->getClient()->getDbVersion(), '2.6', '>=')) {
977
                $this->operator->bitwiceXor($field, $value);
978
            } else {
979
                $this->operator->set($field, $newValue);
980
            }
981
        }
982
983
        return $this;
984
    }
985
986
    public function save($validate = true)
987
    {
988
        // save document
989
        // if document already in db and not modified - skip this method
990
        if (!$this->isSaveRequired()) {
991
            return $this;
992
        }
993
994
        if ($validate) {
995
            $this->validate();
996
        }
997
998
        // handle beforeSave event
999
        if($this->triggerEvent('beforeSave')->isCancelled()) {
1000
            return $this;
1001
        }
1002
        if ($this->isStored()) {
1003
            if($this->triggerEvent('beforeUpdate')->isCancelled()) {
1004
                return $this;
1005
            }
1006
1007
            // locking
1008
            $query = array('_id' => $this->getId());
1009
            if ($this->getOption('lock') === Definition::LOCK_OPTIMISTIC) {
1010
                $query['__version__'] = $this->get('__version__');
1011
                $this->getOperator()->increment('__version__');
1012
            }
1013
1014
            // update
1015
            $status = $this->collection
1016
                ->getMongoCollection()
1017
                ->update(
1018
                    $query,
1019
                    $this->getOperator()->toArray()
1020
                );
1021
1022
            // check update status
1023 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...
1024
                throw new \Sokil\Mongo\Exception(sprintf(
1025
                    'Update error: %s: %s',
1026
                    $status['err'],
1027
                    $status['errmsg']
1028
                ));
1029
            }
1030
1031
            // check if document modified due to specified lock
1032
            if ($this->getOption('lock') === Definition::LOCK_OPTIMISTIC) {
1033
                if($status['n'] === 0) {
1034
                    throw new OptimisticLockFailureException;
1035
                }
1036
            }
1037
1038
            if ($this->getOperator()->isReloadRequired()) {
1039
                $this->refresh();
1040
            } else {
1041
                $this->getOperator()->reset();
1042
            }
1043
1044
            $this->triggerEvent('afterUpdate');
1045
        } else {
1046
            if($this->triggerEvent('beforeInsert')->isCancelled()) {
1047
                return $this;
1048
            }
1049
1050
            $document = $this->toArray();
1051
1052
            $this
1053
                ->collection
1054
                ->getMongoCollection()
1055
                ->insert($document);
1056
            
1057
            // set id
1058
            $this->defineId($document['_id']);
1059
1060
            // after insert event
1061
            $this->triggerEvent('afterInsert');
1062
        }
1063
1064
        // handle afterSave event
1065
        $this->triggerEvent('afterSave');
1066
1067
        $this->apply();
1068
        
1069
        return $this;
1070
    }
1071
1072
    public function isSaveRequired()
1073
    {
1074
        return !$this->isStored() || $this->isModified() || $this->isModificationOperatorDefined();
1075
    }
1076
1077
    public function delete()
1078
    {
1079
        if ($this->triggerEvent('beforeDelete')->isCancelled()) {
1080
            return $this;
1081
        }
1082
1083
        $status = $this->collection->getMongoCollection()->remove(array(
1084
            '_id'   => $this->getId(),
1085
        ));
1086
1087 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...
1088
            throw new \Sokil\Mongo\Exception(sprintf('Delete document error: %s', $status['err']));
1089
        }
1090
1091
        $this->triggerEvent('afterDelete');
1092
1093
        // drop from document's pool
1094
        $this->getCollection()->removeDocumentFromDocumentPool($this);
1095
1096
        return $this;
1097
    }
1098
1099
    /**
1100
     *
1101
     * @return \Sokil\Mongo\RevisionManager
1102
     */
1103
    public function getRevisionManager()
1104
    {
1105
        if(!$this->revisionManager) {
1106
            $this->revisionManager = new RevisionManager($this);
1107
        }
1108
1109
        return $this->revisionManager;
1110
    }
1111
1112
    /**
1113
     * @deprecated since 1.13.0 use self::getRevisionManager()->getRevisions()
1114
     */
1115
    public function getRevisions($limit = null, $offset = null)
1116
    {
1117
        return $this->getRevisionManager()->getRevisions($limit, $offset);
1118
    }
1119
1120
    /**
1121
     * @deprecated since 1.13.0 use self::getRevisionManager()->getRevision()
1122
     */
1123
    public function getRevision($id)
1124
    {
1125
        return $this->getRevisionManager()->getRevision($id);
1126
    }
1127
1128
    /**
1129
     * @deprecated since 1.13.0 use self::getRevisionManager()->getRevisionsCount()
1130
     */
1131
    public function getRevisionsCount()
1132
    {
1133
        return $this->getRevisionManager()->getRevisionsCount();
1134
    }
1135
1136
    /**
1137
     * @deprecated since 1.13.0 use self::getRevisionManager()->clearRevisions()
1138
     */
1139
    public function clearRevisions()
1140
    {
1141
        $this->getRevisionManager()->clearRevisions();
1142
        return $this;
1143
    }
1144
1145
}
1146