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 ( 36ec0a...4aa46a )
by De
02:02
created

Document   F

Complexity

Total Complexity 153

Size/Duplication

Total Lines 1273
Duplicated Lines 2.36 %

Coupling/Cohesion

Components 3
Dependencies 22

Importance

Changes 0
Metric Value
wmc 153
lcom 3
cbo 22
dl 30
loc 1273
rs 0.6314
c 0
b 0
f 0

72 Methods

Rating   Name   Duplication   Size   Complexity  
A getCollection() 0 4 1
A reset() 0 16 1
A refresh() 0 14 1
A initDelegates() 0 14 2
A __toString() 0 4 1
C __call() 0 37 7
A __get() 0 10 2
A setGeometry() 0 4 1
A setPoint() 0 10 1
A setLegacyPoint() 0 7 1
A setLineString() 0 7 1
A setPolygon() 0 7 1
A setMultiPoint() 0 7 1
A setMultiLineString() 0 7 1
A setMultyPolygon() 0 7 1
A setGeometryCollection() 0 7 1
A relations() 0 5 1
A getRelationDefinition() 0 9 2
A getRelationManager() 0 10 2
A getRelated() 0 4 1
A addRelation() 0 6 1
A removeRelation() 0 6 1
A triggerEvent() 0 10 2
A attachEvent() 0 5 1
A hasEvent() 0 4 1
A getId() 0 4 1
B __construct() 0 25 2
A getOptions() 0 4 1
A getOption() 0 4 2
A hasOption() 0 4 1
A beforeConstruct() 0 3 1
A normalizeDocumentId() 0 8 2
A defineId() 0 5 1
A setId() 0 4 1
A isStored() 0 4 2
A validate() 0 19 3
A behaviors() 0 4 1
A attachBehaviors() 0 8 2
B attachBehavior() 0 25 5
A clearBehaviors() 0 5 1
A getOperator() 0 4 1
A isModificationOperatorDefined() 0 4 1
A set() 0 11 2
A unsetField() 0 14 3
A __unset() 0 4 1
A createReference() 0 12 2
A setReference() 0 7 1
A getReferencedDocument() 0 11 2
A pushReference() 0 7 1
C getReferencedDocumentList() 0 34 7
A merge() 0 12 3
A append() 0 11 2
C push() 6 47 9
B pushEach() 4 25 5
D addToSet() 0 45 10
A addToSetEach() 0 4 1
A pull() 0 5 1
A increment() 0 11 2
A decrement() 0 4 1
A bitwiceAnd() 10 10 2
A bitwiceOr() 10 10 2
A bitwiceXor() 0 17 3
B internalInsert() 0 28 3
B internalUpdate() 0 56 8
B save() 0 31 5
A isSaveRequired() 0 4 3
A delete() 0 21 4
A getRevisionManager() 0 8 2
A getRevisions() 0 4 1
A getRevision() 0 4 1
A getRevisionsCount() 0 4 1
A clearRevisions() 0 5 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Document often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Document, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is part of the PHPMongo package.
5
 *
6
 * (c) Dmytro Sokil <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sokil\Mongo;
13
14
use Sokil\Mongo\Document\InvalidOperationException;
15
use Sokil\Mongo\Document\RelationManager;
16
use Sokil\Mongo\Document\RevisionManager;
17
use Sokil\Mongo\Document\InvalidDocumentException;
18
use Sokil\Mongo\Collection\Definition;
19
use Sokil\Mongo\Document\OptimisticLockFailureException;
20
use Sokil\Mongo\Exception\WriteException;
21
use Symfony\Component\EventDispatcher\EventDispatcher;
22
use GeoJson\Geometry\Geometry;
23
24
/**
25
 * Instance of this class is a representation of one document from collection.
26
 *
27
 * @link https://github.com/sokil/php-mongo#document-schema Document schema
28
 * @link https://github.com/sokil/php-mongo#create-new-document Create new document
29
 * @link https://github.com/sokil/php-mongo#get-and-set-data-in-document get and set data
30
 * @link https://github.com/sokil/php-mongo#storing-document Saving document
31
 * @link https://github.com/sokil/php-mongo#document-validation Validation
32
 * @link https://github.com/sokil/php-mongo#deleting-collections-and-documents Deleting documents
33
 * @link https://github.com/sokil/php-mongo#events Event handlers
34
 * @link https://github.com/sokil/php-mongo#behaviors Behaviors
35
 * @link https://github.com/sokil/php-mongo#relations Relations
36
 *
37
 * @method \Sokil\Mongo\Document onAfterConstruct(callable $handler, int $priority = 0)
38
 * @method \Sokil\Mongo\Document onBeforeValidate(callable $handler, int $priority = 0)
39
 * @method \Sokil\Mongo\Document onAfterValidate(callable $handler, int $priority = 0)
40
 * @method \Sokil\Mongo\Document onValidateError(callable $handler, int $priority = 0)
41
 * @method \Sokil\Mongo\Document onBeforeInsert(callable $handler, int $priority = 0)
42
 * @method \Sokil\Mongo\Document onAfterInsert(callable $handler, int $priority = 0)
43
 * @method \Sokil\Mongo\Document onBeforeUpdate(callable $handler, int $priority = 0)
44
 * @method \Sokil\Mongo\Document onAfterUpdate(callable $handler, int $priority = 0)
45
 * @method \Sokil\Mongo\Document onBeforeSave(callable $handler, int $priority = 0)
46
 * @method \Sokil\Mongo\Document onAfterSave(callable $handler, int $priority = 0)
47
 * @method \Sokil\Mongo\Document onBeforeDelete(callable $handler, int $priority = 0)
48
 * @method \Sokil\Mongo\Document onAfterDelete(callable $handler, int $priority = 0)
49
 *
50
 * @author Dmytro Sokil <[email protected]>
51
 */
52
class Document extends Structure
53
{
54
    /**
55
     *
56
     * @var \Sokil\Mongo\Document\RelationManager
57
     */
58
    private $relationManager;
59
60
    const RELATION_HAS_ONE = 'HAS_ONE';
61
    const RELATION_BELONGS = 'BELONGS';
62
    const RELATION_HAS_MANY = 'HAS_MANY';
63
    const RELATION_MANY_MANY = 'MANY_MANY';
64
65
    /**
66
     *
67
     * @var \Sokil\Mongo\Document\RevisionManager
68
     */
69
    private $revisionManager;
70
71
    /**
72
     *
73
     * @var \Sokil\Mongo\Collection
74
     */
75
    private $collection;
76
77
    /**
78
     * @var \Symfony\Component\EventDispatcher\EventDispatcher Event Dispatcher instance
79
     */
80
    private $eventDispatcher;
81
82
    /**
83
     * @var \Sokil\Mongo\Operator Modification operator instance
84
     */
85
    private $operator;
86
87
    /**
88
     *
89
     * @var array list of defined behaviors
90
     */
91
    private $behaviors = array();
92
93
    /**
94
     *
95
     * @var array document options
96
     */
97
    private $options;
98
99
    /**
100
     * @param \Sokil\Mongo\Collection $collection instance of Mongo collection
101
     * @param array $data mongo document
102
     * @param array $options options of object initialization
103
     */
104
    public function __construct(Collection $collection, array $data = null, array $options = array())
105
    {
106
        // lisk to collection
107
        $this->collection = $collection;
108
109
        // configure document with options
110
        $this->options = $options;
111
112
        // init document
113
        $this->initDelegates();
114
115
        // execute before construct callable
116
        $this->beforeConstruct();
117
118
        // initialize with data
119
        parent::__construct($data, $this->getOption('stored'));
120
121
        // use versioning
122
        if ($this->getOption('versioning')) {
123
            $this->getRevisionManager()->listen();
124
        }
125
126
        // execure after construct event handlers
127
        $this->eventDispatcher->dispatch('afterConstruct');
128
    }
129
130
    public function getOptions()
131
    {
132
        return $this->options;
133
    }
134
135
    public function getOption($name, $default = null)
136
    {
137
        return isset($this->options[$name]) ? $this->options[$name] : $default;
138
    }
139
140
    public function hasOption($name)
141
    {
142
        return isset($this->options[$name]);
143
    }
144
145
    /**
146
     * Event handler, called before running constructor.
147
     * May be overridden in child classes
148
     */
149
    public function beforeConstruct()
150
    {
151
    }
152
153
    /**
154
     * Get instance of collection
155
     * @return \Sokil\Mongo\Collection
156
     */
157
    public function getCollection()
158
    {
159
        return $this->collection;
160
    }
161
162
    /**
163
     * Reset all data passed to object in run-time, like events, behaviors,
164
     * data modifications, etc. to the state just after open or save document
165
     *
166
     * @return \Sokil\Mongo\Document
167
     */
168
    public function reset()
169
    {
170
        // reset structure
171
        parent::reset();
172
173
        // reset errors
174
        $this->clearErrors();
175
176
        // reset behaviors
177
        $this->clearBehaviors();
178
179
        // init delegates
180
        $this->initDelegates();
181
182
        return $this;
183
    }
184
185
    /**
186
     * Reload data from db and reset all unsaved data
187
     */
188
    public function refresh()
189
    {
190
        $data = $this->collection
191
            ->getMongoCollection()
192
            ->findOne(array(
193
                '_id' => $this->getId()
194
            ));
195
196
        $this->replace($data);
197
198
        $this->operator->reset();
199
200
        return $this;
201
    }
202
203
    /**
204
     * Initialise relative classes
205
     */
206
    private function initDelegates()
207
    {
208
        // start event dispatching
209
        $this->eventDispatcher = new EventDispatcher;
210
        
211
        // create operator
212
        $this->operator = $this->getCollection()->operator();
213
214
        // attach behaviors
215
        $this->attachBehaviors($this->behaviors());
216
        if ($this->hasOption('behaviors')) {
217
            $this->attachBehaviors($this->getOption('behaviors'));
218
        }
219
    }
220
221
    public function __toString()
222
    {
223
        return (string) $this->getId();
224
    }
225
226
    public function __call($name, $arguments)
227
    {
228
        // behaviors
229
        foreach ($this->behaviors as $behavior) {
230
            if (!method_exists($behavior, $name)) {
231
                continue;
232
            }
233
234
            return call_user_func_array(array($behavior, $name), $arguments);
235
        }
236
237
        // adding event
238
        if ('on' === substr($name, 0, 2)) {
239
            // prepend event name to function args
240
            $addListenerArguments = $arguments;
241
            array_unshift($addListenerArguments, lcfirst(substr($name, 2)));
242
            // add listener
243
            call_user_func_array(
244
                array($this->eventDispatcher, 'addListener'),
245
                $addListenerArguments
246
            );
247
            
248
            return $this;
249
        }
250
251
        // getter
252
        if ('get' === strtolower(substr($name, 0, 3))) {
253
            return $this->get(lcfirst(substr($name, 3)));
254
        }
255
256
        // setter
257
        if ('set' === strtolower(substr($name, 0, 3)) && isset($arguments[0])) {
258
            return $this->set(lcfirst(substr($name, 3)), $arguments[0]);
259
        }
260
261
        throw new Exception('Document has no method "' . $name . '"');
262
    }
263
264
    public function __get($name)
265
    {
266
        if ($this->getRelationManager()->isRelationExists($name)) {
267
            // resolve relation
268
            return $this->getRelationManager()->getRelated($name);
269
        } else {
270
            // get document parameter
271
            return parent::__get($name);
272
        }
273
    }
274
275
    /**
276
     * Set geo data as GeoJson object
277
     *
278
     * Requires MongoDB version 2.4 or above with 2dsparse index version 1
279
     * to use Point, LineString and Polygon.
280
     *
281
     * Requires MongoDB version 2.6 or above with 2dsparse index version 2
282
     * to use MultiPoint, MultiLineString, MultiPolygon and GeometryCollection.
283
     *
284
     * @link http://geojson.org/
285
     * @param string $field
286
     * @param \GeoJson\Geometry\Geometry $geometry
287
     * @return \Sokil\Mongo\Document
288
     */
289
    public function setGeometry($field, Geometry $geometry)
290
    {
291
        return $this->set($field, $geometry);
292
    }
293
294
    /**
295
     * Set point as longitude and latitude
296
     *
297
     * Requires MongoDB version 2.4 or above with 2dsparse index version 1
298
     * to use Point, LineString and Polygon.
299
     *
300
     * @link http://docs.mongodb.org/manual/core/2dsphere/#point
301
     * @param string $field
302
     * @param float $longitude
303
     * @param float $latitude
304
     * @return \Sokil\Mongo\Document
305
     */
306
    public function setPoint($field, $longitude, $latitude)
307
    {
308
        return $this->setGeometry(
309
            $field,
310
            new \GeoJson\Geometry\Point(array(
311
                $longitude,
312
                $latitude
313
            ))
314
        );
315
    }
316
317
    /**
318
     * Set point as longitude and latitude in legacy format
319
     *
320
     * May be used 2d index
321
     *
322
     * @link http://docs.mongodb.org/manual/core/2d/#geospatial-indexes-store-grid-coordinates
323
     * @param string $field
324
     * @param float $longitude
325
     * @param float $latitude
326
     * @return \Sokil\Mongo\Document
327
     */
328
    public function setLegacyPoint($field, $longitude, $latitude)
329
    {
330
        return $this->set(
331
            $field,
332
            array($longitude, $latitude)
333
        );
334
    }
335
336
    /**
337
     * Set line string as array of points
338
     *
339
     * Requires MongoDB version 2.4 or above with 2dsparse index version 1
340
     * to use Point, LineString and Polygon.
341
     *
342
     * @link http://docs.mongodb.org/manual/core/2dsphere/#linestring
343
     * @param string $field
344
     * @param array $pointArray array of points
345
     * @return \Sokil\Mongo\Document
346
     */
347
    public function setLineString($field, array $pointArray)
348
    {
349
        return $this->setGeometry(
350
            $field,
351
            new \GeoJson\Geometry\LineString($pointArray)
352
        );
353
    }
354
355
    /**
356
     * Set polygon as array of line rings.
357
     *
358
     * Line ring is closed line string (first and last point same).
359
     * Line string is array of points.
360
     *
361
     * Requires MongoDB version 2.4 or above with 2dsparse index version 1
362
     * to use Point, LineString and Polygon.
363
     *
364
     * @link http://docs.mongodb.org/manual/core/2dsphere/#polygon
365
     * @param string $field
366
     * @param array $lineRingsArray array of line rings
367
     * @return \Sokil\Mongo\Document
368
     */
369
    public function setPolygon($field, array $lineRingsArray)
370
    {
371
        return $this->setGeometry(
372
            $field,
373
            new \GeoJson\Geometry\Polygon($lineRingsArray)
374
        );
375
    }
376
377
    /**
378
     * Set multi point as array of points
379
     *
380
     * Requires MongoDB version 2.6 or above with 2dsparse index version 2
381
     * to use MultiPoint, MultiLineString, MultiPolygon and GeometryCollection.
382
     *
383
     * @link http://docs.mongodb.org/manual/core/2dsphere/#multipoint
384
     * @param string $field
385
     * @param array $pointArray array of point arrays
386
     * @return \Sokil\Mongo\Document
387
     */
388
    public function setMultiPoint($field, $pointArray)
389
    {
390
        return $this->setGeometry(
391
            $field,
392
            new \GeoJson\Geometry\MultiPoint($pointArray)
393
        );
394
    }
395
396
    /**
397
     * Set multi line string as array of line strings
398
     *
399
     * Requires MongoDB version 2.6 or above with 2dsparse index version 2
400
     * to use MultiPoint, MultiLineString, MultiPolygon and GeometryCollection.
401
     *
402
     * http://docs.mongodb.org/manual/core/2dsphere/#multilinestring
403
     * @param string $field
404
     * @param array $lineStringArray array of line strings
405
     * @return \Sokil\Mongo\Document
406
     */
407
    public function setMultiLineString($field, $lineStringArray)
408
    {
409
        return $this->setGeometry(
410
            $field,
411
            new \GeoJson\Geometry\MultiLineString($lineStringArray)
412
        );
413
    }
414
415
    /**
416
     * Set multy polygon as array of polygons.
417
     *
418
     * Polygon is array of line rings.
419
     * Line ring is closed line string (first and last point same).
420
     * Line string is array of points.
421
     *
422
     * Requires MongoDB version 2.6 or above with 2dsparse index version 2
423
     * to use MultiPoint, MultiLineString, MultiPolygon and GeometryCollection.
424
     *
425
     * @link http://docs.mongodb.org/manual/core/2dsphere/#multipolygon
426
     * @param string $field
427
     * @param array $polygonsArray array of polygons
428
     * @return \Sokil\Mongo\Document
429
     */
430
    public function setMultyPolygon($field, array $polygonsArray)
431
    {
432
        return $this->setGeometry(
433
            $field,
434
            new \GeoJson\Geometry\MultiPolygon($polygonsArray)
435
        );
436
    }
437
438
    /**
439
     * Set collection of different geometries
440
     *
441
     * Requires MongoDB version 2.6 or above with 2dsparse index version 2
442
     * to use MultiPoint, MultiLineString, MultiPolygon and GeometryCollection.
443
     *
444
     * @link http://docs.mongodb.org/manual/core/2dsphere/#geometrycollection
445
     * @param string $field
446
     * @param array $geometryCollection
447
     * @return \Sokil\Mongo\Document
448
     */
449
    public function setGeometryCollection($field, array $geometryCollection)
450
    {
451
        return $this->setGeometry(
452
            $field,
453
            new \GeoJson\Geometry\GeometryCollection($geometryCollection)
454
        );
455
    }
456
457
    /**
458
     * Override in child class to define relations
459
     * @return array relation description
460
     */
461
    protected function relations()
462
    {
463
        // [relationName => [relationType, targetCollection, reference], ...]
0 ignored issues
show
Unused Code Comprehensibility introduced by
48% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
464
        return array();
465
    }
466
467
    /**
468
     * Relation definition through mapping is more prior to defined in class
469
     * @return array definition of relations
470
     */
471
    public function getRelationDefinition()
472
    {
473
        $relations = $this->getOption('relations');
474
        if (!is_array($relations)) {
475
            return $this->relations();
476
        }
477
478
        return $relations + $this->relations();
479
    }
480
481
    /**
482
     *
483
     * @return \Sokil\Mongo\Document\RelationManager
484
     */
485
    private function getRelationManager()
486
    {
487
        if ($this->relationManager) {
488
            return $this->relationManager;
489
        }
490
491
        $this->relationManager = new RelationManager($this);
492
493
        return $this->relationManager;
494
    }
495
496
    /**
497
     * Get related documents
498
     * @param string $relationName
499
     * @return array|\Sokil\Mongo\Document related document or array of documents
500
     */
501
    public function getRelated($relationName)
502
    {
503
        return $this->getRelationManager()->getRelated($relationName);
504
    }
505
506
    public function addRelation($relationName, Document $document)
507
    {
508
        $this->getRelationManager()->addRelation($relationName, $document);
509
510
        return $this;
511
    }
512
513
    public function removeRelation($relationName, Document $document = null)
514
    {
515
        $this->getRelationManager()->removeRelation($relationName, $document);
516
517
        return $this;
518
    }
519
520
    /**
521
     * Manually trigger defined events
522
     * @param string $eventName event name
523
     * @return \Sokil\Mongo\Event
524
     */
525
    public function triggerEvent($eventName, Event $event = null)
526
    {
527
        if (!$event) {
528
            $event = new Event;
529
        }
530
531
        $event->setTarget($this);
532
533
        return $this->eventDispatcher->dispatch($eventName, $event);
534
    }
535
536
    /**
537
     * Attach event handler
538
     * @param string $event event name
539
     * @param callable|array|string $handler event handler
540
     * @return \Sokil\Mongo\Document
541
     */
542
    public function attachEvent($event, $handler, $priority = 0)
543
    {
544
        $this->eventDispatcher->addListener($event, $handler, $priority);
545
        return $this;
546
    }
547
548
    /**
549
     * Check if event attached
550
     *
551
     * @param string $event event name
552
     * @return bool
553
     */
554
    public function hasEvent($event)
555
    {
556
        return $this->eventDispatcher->hasListeners($event);
557
    }
558
559
    public function getId()
560
    {
561
        return $this->get('_id');
562
    }
563
564
    /**
565
     * Get normalized id of document. If id is valid ObjectId, convert it to \MongoId
566
     *
567
     * @param \MongoId|string|int $id document identifier
568
     *
569
     * @return \MongoId|string|int
570
     */
571
    private function normalizeDocumentId($id)
572
    {
573
        if (\MongoId::isValid($id)) {
574
            return new \MongoId($id);
575
        } else {
576
            return $id;
577
        }
578
    }
579
580
    /**
581
     * Used to define id of stored document.
582
     * This id must be already present in db
583
     *
584
     * @param \MongoId|string|int $id document identifier
585
     *
586
     * @return Document
587
     */
588
    public function defineId($id)
589
    {
590
        $this->mergeUnmodified(array('_id' => $this->normalizeDocumentId($id)));
591
        return $this;
592
    }
593
594
    /**
595
     * Used to define id of not stored document or chane id of stored document.
596
     *
597
     * @param \MongoId|string $id id of document
598
     *
599
     * @return Document
600
     */
601
    public function setId($id)
602
    {
603
        return $this->set('_id', $this->normalizeDocumentId($id));
604
    }
605
606
    /**
607
     * Check if document is stored
608
     *
609
     * @return bool
610
     */
611
    public function isStored()
612
    {
613
        return $this->get('_id') && !$this->isModified('_id');
614
    }
615
616
    /**
617
     * Validate document
618
     *
619
     * @throws \Sokil\Mongo\Document\InvalidDocumentException
620
     * @return \Sokil\Mongo\Document
621
     */
622
    public function validate()
623
    {
624
        if ($this->triggerEvent('beforeValidate')->isCancelled()) {
625
            return $this;
626
        }
627
628
        if (!$this->isValid()) {
629
            $exception = new InvalidDocumentException('Document not valid');
630
            $exception->setDocument($this);
631
632
            $this->triggerEvent('validateError');
633
634
            throw $exception;
635
        }
636
637
        $this->triggerEvent('afterValidate');
638
639
        return $this;
640
    }
641
642
    public function behaviors()
643
    {
644
        return array();
645
    }
646
647
    public function attachBehaviors(array $behaviors)
648
    {
649
        foreach ($behaviors as $name => $behavior) {
650
            $this->attachBehavior($name, $behavior);
651
        }
652
653
        return $this;
654
    }
655
656
    /**
657
     *
658
     * @param string $name unique name of attached behavior
659
     * @param string|array|\Sokil\Mongo\Behavior $behavior Behavior instance or behavior definition
660
     * @return \Sokil\Mongo\Document
661
     * @throws Exception
662
     */
663
    public function attachBehavior($name, $behavior)
664
    {
665
        if (is_string($behavior)) {
666
            // behavior defined as string
667
            $className = $behavior;
668
            $behavior = new $className();
669
        } elseif (is_array($behavior)) {
670
            // behavior defined as array
671
            if (empty($behavior['class'])) {
672
                throw new Exception('Behavior class not specified');
673
            }
674
            $className = $behavior['class'];
675
            unset($behavior['class']);
676
            $behavior = new $className($behavior);
677
        } elseif (!($behavior instanceof Behavior)) {
678
            // behavior bust be Behavior instance, but something else found
679
            throw new Exception('Wrong behavior specified with name ' . $name);
680
        }
681
682
        $behavior->setOwner($this);
683
684
        $this->behaviors[$name] = $behavior;
685
686
        return $this;
687
    }
688
689
    public function clearBehaviors()
690
    {
691
        $this->behaviors = array();
692
        return $this;
693
    }
694
695
    public function getOperator()
696
    {
697
        return $this->operator;
698
    }
699
700
    public function isModificationOperatorDefined()
701
    {
702
        return $this->operator->isDefined();
703
    }
704
705
    /**
706
     * Update value in local cache and in DB
707
     *
708
     * @param string $fieldName point-delimited field name
709
     * @param mixed $value value to store
710
     * @return \Sokil\Mongo\Document
711
     */
712
    public function set($fieldName, $value)
713
    {
714
        parent::set($fieldName, $value);
715
716
        // if document saved - save through update
717
        if ($this->getId()) {
718
            $this->operator->set($fieldName, $value);
719
        }
720
721
        return $this;
722
    }
723
724
    /**
725
     * Remove field
726
     *
727
     * @param string $fieldName field name
728
     * @return \Sokil\Mongo\Document
729
     */
730
    public function unsetField($fieldName)
731
    {
732
        if (!$this->has($fieldName)) {
733
            return $this;
734
        }
735
736
        parent::unsetField($fieldName);
737
738
        if ($this->getId()) {
739
            $this->operator->unsetField($fieldName);
740
        }
741
742
        return $this;
743
    }
744
745
    public function __unset($fieldName)
746
    {
747
        $this->unsetField($fieldName);
748
    }
749
750
    /**
751
     * Get reference to document
752
     *
753
     * @throws Exception
754
     * @return array
755
     */
756
    public function createReference()
757
    {
758
        $documentId = $this->getId();
759
        if (null === $documentId) {
760
            throw new Exception('Document must be stored to get DBRef');
761
        }
762
763
        return $this
764
            ->getCollection()
765
            ->getMongoCollection()
766
            ->createDBRef($documentId);
767
    }
768
769
    /**
770
     * Store DBRef to specified field
771
     *
772
     * @param $name
773
     * @param Document $document
774
     * @return Document
775
     */
776
    public function setReference($name, Document $document)
777
    {
778
        return $this->set(
779
            $name,
780
            $document->createReference()
781
        );
782
    }
783
784
    /**
785
     * Get document by reference
786
     *
787
     * @param string    $name   name of field where reference stored
788
     * @return null|Document
789
     */
790
    public function getReferencedDocument($name)
791
    {
792
        $reference = $this->get($name);
793
        if (null === $reference) {
794
            return null;
795
        }
796
797
        return $this->collection
798
            ->getDatabase()
799
            ->getDocumentByReference($reference);
800
    }
801
802
    /**
803
     * Push reference to list
804
     *
805
     * @param string $name
806
     * @param Document $document
807
     * @return Document
808
     */
809
    public function pushReference($name, Document $document)
810
    {
811
        return $this->push(
812
            $name,
813
            $document->createReference()
814
        );
815
    }
816
817
    /**
818
     * Get document by reference
819
     *
820
     * @param string    $name   name of field where reference stored
821
     * @return null|Document
822
     */
823
    public function getReferencedDocumentList($name)
824
    {
825
        $referenceList = $this->get($name);
826
        if (null === $referenceList) {
827
            return null;
828
        }
829
830
        if (!isset($referenceList[0])) {
831
            throw new Exception('List of references not found');
832
        }
833
834
        // build list of referenced collections and ids
835
        $documentIdList = array();
836
        foreach ($referenceList as $reference) {
837
            if (empty($reference['$ref']) || empty($reference['$id'])) {
838
                throw new Exception(sprintf(
839
                    'Iinvalid reference in list for document %s in field %s',
840
                    $this->getId(),
841
                    $name
842
                ));
843
            }
844
845
            $documentIdList[$reference['$ref']][] = $reference['$id'];
846
        }
847
848
        // get list
849
        $documentList = array();
850
        $database = $this->collection->getDatabase();
851
        foreach ($documentIdList as $collectionName => $documentIdList) {
852
            $documentList += $database->getCollection($collectionName)->find()->byIdList($documentIdList)->findAll();
853
        }
854
855
        return $documentList;
856
    }
857
858
    /**
859
     * @param array $data
860
     * @return Document
861
     */
862
    public function merge(array $data)
863
    {
864
        if ($this->isStored()) {
865
            foreach ($data as $fieldName => $value) {
866
                $this->set($fieldName, $value);
867
            }
868
        } else {
869
            parent::merge($data);
870
        }
871
872
        return $this;
873
    }
874
875
    /**
876
     * If field not exist - set value.
877
     * If field exists and is not array - convert to array and append
878
     * If field is array - append
879
     *
880
     * @param string $selector
881
     * @param mixed $value
882
     * @return Document
883
     */
884
    public function append($selector, $value)
885
    {
886
        parent::append($selector, $value);
887
888
        // if document saved - save through update
889
        if ($this->getId()) {
890
            $this->operator->set($selector, $this->get($selector));
891
        }
892
893
        return $this;
894
    }
895
896
    /**
897
     * Push argument as single element to field value
898
     *
899
     * @param string $fieldName
900
     * @param mixed $value
901
     * @return \Sokil\Mongo\Document
902
      */
903
    public function push($fieldName, $value)
904
    {
905
        $oldValue = $this->get($fieldName);
906
907
        // check if old value is list or sub document
908
        // on sub document throw exception
909
        if (is_array($oldValue)) {
910
            $isSubDocument = (array_keys($oldValue) !== range(0, count($oldValue) - 1));
911
            if ($isSubDocument) {
912
                throw new InvalidOperationException(sprintf('The field "%s" must be an array but is of type Object', $fieldName));
913
            }
914
        }
915
916
        // prepare new value
917
        $value = Structure::prepareToStore($value);
918
919
        // field not exists
920
        if (!$oldValue) {
921
            if ($this->getId()) {
922
                $this->operator->push($fieldName, $value);
923
            }
924
            $value = array($value);
925
        } // field already exist and has single value
926 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...
927
            $value = array_merge((array) $oldValue, array($value));
928
            if ($this->getId()) {
929
                $this->operator->set($fieldName, $value);
930
            }
931
        } // field exists and is array
932
        else {
933
            if ($this->getId()) {
934
                $setValue = $this->operator->get('$set', $fieldName);
935
                if ($setValue) {
936
                    $setValue[] = $value;
937
                    $this->operator->set($fieldName, $setValue);
938
                } else {
939
                    $this->operator->push($fieldName, $value);
940
                }
941
            }
942
            $value = array_merge($oldValue, array($value));
943
        }
944
945
        // update local data
946
        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...
947
948
        return $this;
949
    }
950
951
    /**
952
     * Push each element of argument's array as single element to field value
953
     *
954
     * @param string $fieldName
955
     * @param array $values
956
     * @return \Sokil\Mongo\Document
957
     */
958
    public function pushEach($fieldName, array $values)
959
    {
960
        $oldValue = $this->get($fieldName);
961
962
        if ($this->getId()) {
963
            if (!$oldValue) {
964
                $this->operator->pushEach($fieldName, $values);
965 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...
966
                $values = array_merge((array) $oldValue, $values);
967
                $this->operator->set($fieldName, $values);
968
            } else {
969
                $this->operator->pushEach($fieldName, $values);
970
                $values = array_merge($oldValue, $values);
971
            }
972
        } else {
973
            if ($oldValue) {
974
                $values = array_merge((array) $oldValue, $values);
975
            }
976
        }
977
978
        // update local data
979
        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...
980
981
        return $this;
982
    }
983
984
    public function addToSet($fieldName, $value)
985
    {
986
        $set = $this->get($fieldName);
987
988
        // prepare new value
989
        $value = Structure::prepareToStore($value);
990
991
        // add to set
992
        if (empty($set)) {
993
            $updatedSet = array($value);
994
            if ($this->getId()) {
995
                $this->operator->addToSet($fieldName, $value);
996
            }
997
        } elseif (!is_array($set)) {
998
            if ($set === $value) {
999
                return $this;
1000
            }
1001
            $updatedSet = array($set, $value);
1002
            if ($this->getId()) {
1003
                $this->operator->set($fieldName, $updatedSet);
1004
            }
1005
        } elseif (array_keys($set) !== range(0, count($set) - 1)) {
1006
            // check if old value is list or sub document
1007
            // on sub document throw exception
1008
            throw new InvalidOperationException(sprintf('The field "%s" must be an array but is of type Object', $fieldName));
1009
        } else {
1010
            // check if already in set
1011
            if (in_array($value, $set, true)) {
1012
                return $this;
1013
            }
1014
            $updatedSet = array_merge($set, array($value));
1015
            if ($this->getId()) {
1016
                $setValue = $this->operator->get('$set', $fieldName);
1017
                if ($setValue) {
1018
                    $this->operator->set($fieldName, $updatedSet);
1019
                } else {
1020
                    $this->operator->addToSet($fieldName, $value);
1021
                }
1022
            }
1023
        }
1024
1025
        parent::set($fieldName, $updatedSet);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (set() instead of addToSet()). Are you sure this is correct? If so, you might want to change this to $this->set().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
1026
1027
        return $this;
1028
    }
1029
1030
    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...
1031
    {
1032
        throw new \RuntimeException('Not implemented');
1033
    }
1034
1035
    /**
1036
     * Removes from an existing array all instances of a value or
1037
     * values that match a specified query
1038
     *
1039
     * @param integer|string|array|\Sokil\Mongo\Expression|callable $expression
1040
     * @param mixed|\Sokil\Mongo\Expression|callable $value
1041
     * @return \Sokil\Mongo\Document
1042
     */
1043
    public function pull($expression, $value = null)
1044
    {
1045
        $this->operator->pull($expression, $value);
1046
        return $this;
1047
    }
1048
1049
    public function increment($fieldName, $value = 1)
1050
    {
1051
        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...
1052
1053
        if ($this->getId()) {
1054
            $this->operator->increment($fieldName, $value);
1055
        }
1056
1057
1058
        return $this;
1059
    }
1060
1061
    public function decrement($fieldName, $value = 1)
1062
    {
1063
        return $this->increment($fieldName, -1 * $value);
1064
    }
1065
1066 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...
1067
    {
1068
        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...
1069
1070
        if ($this->getId()) {
1071
            $this->operator->bitwiceAnd($field, $value);
1072
        }
1073
1074
        return $this;
1075
    }
1076
1077 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...
1078
    {
1079
        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...
1080
1081
        if ($this->getId()) {
1082
            $this->operator->bitwiceOr($field, $value);
1083
        }
1084
1085
        return $this;
1086
    }
1087
1088
    public function bitwiceXor($field, $value)
1089
    {
1090
        $oldFieldValue = (int) $this->get($field);
1091
        $newValue = $oldFieldValue ^ $value;
1092
1093
        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...
1094
1095
        if ($this->getId()) {
1096
            if (version_compare($this->getCollection()->getDatabase()->getClient()->getDbVersion(), '2.6', '>=')) {
1097
                $this->operator->bitwiceXor($field, $value);
1098
            } else {
1099
                $this->operator->set($field, $newValue);
1100
            }
1101
        }
1102
1103
        return $this;
1104
    }
1105
1106
    /**
1107
     * Internal method to insert document
1108
     */
1109
    private function internalInsert()
1110
    {
1111
        if ($this->triggerEvent('beforeInsert')->isCancelled()) {
1112
            return;
1113
        }
1114
1115
        $document = $this->toArray();
1116
1117
        // try write document
1118
        try {
1119
            $this
1120
                ->collection
1121
                ->getMongoCollection()
1122
                ->insert($document);
1123
        } catch (\Exception $e) {
1124
            throw new WriteException(
1125
                $e->getMessage(),
1126
                $e->getCode(),
1127
                $e
1128
            );
1129
        }
1130
1131
        // set id
1132
        $this->defineId($document['_id']);
1133
1134
        // after insert event
1135
        $this->triggerEvent('afterInsert');
1136
    }
1137
1138
    /**
1139
     * Internal method to update document
1140
     *
1141
     * @throws WriteException
1142
     * @throws OptimisticLockFailureException
1143
     */
1144
    private function internalUpdate()
1145
    {
1146
        if ($this->triggerEvent('beforeUpdate')->isCancelled()) {
1147
            return;
1148
        }
1149
1150
        // locking
1151
        $query = array('_id' => $this->getId());
1152
        if ($this->getOption('lock') === Definition::LOCK_OPTIMISTIC) {
1153
            $query['__version__'] = $this->get('__version__');
1154
            $this->getOperator()->increment('__version__');
1155
        }
1156
1157
        // update
1158
        try {
1159
            $status = $this
1160
                ->collection
1161
                ->getMongoCollection()
1162
                ->update(
1163
                    $query,
1164
                    $this->getOperator()->toArray()
1165
                );
1166
        } catch (\Exception $e) {
1167
            throw new WriteException(
1168
                $e->getMessage(),
1169
                $e->getCode(),
1170
                $e
1171
            );
1172
        }
1173
1174
        // check update status
1175
        if ($status['ok'] != 1) {
1176
            throw new WriteException(
1177
                sprintf(
1178
                    'Update error: %s: %s',
1179
                    $status['err'],
1180
                    $status['errmsg']
1181
                )
1182
            );
1183
        }
1184
1185
        // check if document modified due to specified lock
1186
        if ($this->getOption('lock') === Definition::LOCK_OPTIMISTIC) {
1187
            if ($status['n'] === 0) {
1188
                throw new OptimisticLockFailureException;
1189
            }
1190
        }
1191
1192
        if ($this->getOperator()->isReloadRequired()) {
1193
            $this->refresh();
1194
        } else {
1195
            $this->getOperator()->reset();
1196
        }
1197
1198
        $this->triggerEvent('afterUpdate');
1199
    }
1200
1201
    /**
1202
     * Save document
1203
     *
1204
     * @param bool $validate
1205
     * @return Document
1206
     * @throws WriteException
1207
     */
1208
    public function save($validate = true)
1209
    {
1210
        // if document already in db and not modified - skip this method
1211
        if (!$this->isSaveRequired()) {
1212
            return $this;
1213
        }
1214
1215
        if ($validate) {
1216
            $this->validate();
1217
        }
1218
1219
        // handle beforeSave event
1220
        if ($this->triggerEvent('beforeSave')->isCancelled()) {
1221
            return $this;
1222
        }
1223
1224
        // write document
1225
        if ($this->isStored()) {
1226
            $this->internalUpdate();
1227
        } else {
1228
            $this->internalInsert();
1229
        }
1230
1231
        // handle afterSave event
1232
        $this->triggerEvent('afterSave');
1233
1234
        // set document unmodified
1235
        $this->apply();
1236
        
1237
        return $this;
1238
    }
1239
1240
    /**
1241
     * Check if document require save
1242
     *
1243
     * @return bool
1244
     */
1245
    public function isSaveRequired()
1246
    {
1247
        return !$this->isStored() || $this->isModified() || $this->isModificationOperatorDefined();
1248
    }
1249
1250
    /**
1251
     * Delete document
1252
     *
1253
     * @return Document
1254
     * @throws Exception
1255
     */
1256
    public function delete()
1257
    {
1258
        if ($this->triggerEvent('beforeDelete')->isCancelled()) {
1259
            return $this;
1260
        }
1261
1262
        $status = $this->collection->getMongoCollection()->remove(array(
1263
            '_id'   => $this->getId(),
1264
        ));
1265
1266
        if (true !== $status && $status['ok'] != 1) {
1267
            throw new \Sokil\Mongo\Exception(sprintf('Delete document error: %s', $status['err']));
1268
        }
1269
1270
        $this->triggerEvent('afterDelete');
1271
1272
        // drop from document's pool
1273
        $this->getCollection()->removeDocumentFromDocumentPool($this);
1274
1275
        return $this;
1276
    }
1277
1278
    /**
1279
     * Get revisions manager
1280
     *
1281
     * @return \Sokil\Mongo\Document\RevisionManager
1282
     */
1283
    public function getRevisionManager()
1284
    {
1285
        if (!$this->revisionManager) {
1286
            $this->revisionManager = new RevisionManager($this);
1287
        }
1288
1289
        return $this->revisionManager;
1290
    }
1291
1292
    /**
1293
     * @deprecated since 1.13.0 use self::getRevisionManager()->getRevisions()
1294
     */
1295
    public function getRevisions($limit = null, $offset = null)
1296
    {
1297
        return $this->getRevisionManager()->getRevisions($limit, $offset);
1298
    }
1299
1300
    /**
1301
     * @deprecated since 1.13.0 use self::getRevisionManager()->getRevision()
1302
     */
1303
    public function getRevision($id)
1304
    {
1305
        return $this->getRevisionManager()->getRevision($id);
1306
    }
1307
1308
    /**
1309
     * @deprecated since 1.13.0 use self::getRevisionManager()->getRevisionsCount()
1310
     */
1311
    public function getRevisionsCount()
1312
    {
1313
        return $this->getRevisionManager()->getRevisionsCount();
1314
    }
1315
1316
    /**
1317
     * @deprecated since 1.13.0 use self::getRevisionManager()->clearRevisions()
1318
     */
1319
    public function clearRevisions()
1320
    {
1321
        $this->getRevisionManager()->clearRevisions();
1322
        return $this;
1323
    }
1324
}
1325