Completed
Push — master ( 056d89...fc4c80 )
by Maciej
10s
created

ClassMetadata::isNullable()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3.072

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 5
cp 0.8
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
nc 3
nop 1
crap 3.072
1
<?php
2
3
namespace Doctrine\ODM\MongoDB\Mapping;
4
5
use Doctrine\Common\Persistence\Mapping\ClassMetadata as BaseClassMetadata;
6
use Doctrine\Instantiator\Instantiator;
7
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
8
use Doctrine\ODM\MongoDB\LockException;
9
use Doctrine\ODM\MongoDB\Proxy\Proxy;
10
use Doctrine\ODM\MongoDB\Types\Type;
11
use InvalidArgumentException;
12
13
/**
14
 * A <tt>ClassMetadata</tt> instance holds all the object-document mapping metadata
15
 * of a document and it's references.
16
 *
17
 * Once populated, ClassMetadata instances are usually cached in a serialized form.
18
 *
19
 * <b>IMPORTANT NOTE:</b>
20
 *
21
 * The fields of this class are only public for 2 reasons:
22
 * 1) To allow fast READ access.
23
 * 2) To drastically reduce the size of a serialized instance (private/protected members
24
 *    get the whole class name, namespace inclusive, prepended to every property in
25
 *    the serialized representation).
26
 *
27
 * @since       1.0
28
 */
29
class ClassMetadata implements BaseClassMetadata
30
{
31
    /* The Id generator types. */
32
    /**
33
     * AUTO means Doctrine will automatically create a new \MongoDB\BSON\ObjectId instance for us.
34
     */
35
    const GENERATOR_TYPE_AUTO = 1;
36
37
    /**
38
     * INCREMENT means a separate collection is used for maintaining and incrementing id generation.
39
     * Offers full portability.
40
     */
41
    const GENERATOR_TYPE_INCREMENT = 2;
42
43
    /**
44
     * UUID means Doctrine will generate a uuid for us.
45
     */
46
    const GENERATOR_TYPE_UUID = 3;
47
48
    /**
49
     * ALNUM means Doctrine will generate Alpha-numeric string identifiers, using the INCREMENT
50
     * generator to ensure identifier uniqueness
51
     */
52
    const GENERATOR_TYPE_ALNUM = 4;
53
54
    /**
55
     * CUSTOM means Doctrine expect a class parameter. It will then try to initiate that class
56
     * and pass other options to the generator. It will throw an Exception if the class
57
     * does not exist or if an option was passed for that there is not setter in the new
58
     * generator class.
59
     *
60
     * The class  will have to be a subtype of AbstractIdGenerator.
61
     */
62
    const GENERATOR_TYPE_CUSTOM = 5;
63
64
    /**
65
     * NONE means Doctrine will not generate any id for us and you are responsible for manually
66
     * assigning an id.
67
     */
68
    const GENERATOR_TYPE_NONE = 6;
69
70
    /**
71
     * Default discriminator field name.
72
     *
73
     * This is used for associations value for associations where a that do not define a "targetDocument" or
74
     * "discriminatorField" option in their mapping.
75
     */
76
    const DEFAULT_DISCRIMINATOR_FIELD = '_doctrine_class_name';
77
78
    const REFERENCE_ONE = 1;
79
    const REFERENCE_MANY = 2;
80
    const EMBED_ONE = 3;
81
    const EMBED_MANY = 4;
82
    const MANY = 'many';
83
    const ONE = 'one';
84
85
    /**
86
     * The types of storeAs references
87
     */
88
    const REFERENCE_STORE_AS_ID = 'id';
89
    const REFERENCE_STORE_AS_DB_REF = 'dbRef';
90
    const REFERENCE_STORE_AS_DB_REF_WITH_DB = 'dbRefWithDb';
91
    const REFERENCE_STORE_AS_REF = 'ref';
92
93
    /* The inheritance mapping types */
94
    /**
95
     * NONE means the class does not participate in an inheritance hierarchy
96
     * and therefore does not need an inheritance mapping type.
97
     */
98
    const INHERITANCE_TYPE_NONE = 1;
99
100
    /**
101
     * SINGLE_COLLECTION means the class will be persisted according to the rules of
102
     * <tt>Single Collection Inheritance</tt>.
103
     */
104
    const INHERITANCE_TYPE_SINGLE_COLLECTION = 2;
105
106
    /**
107
     * COLLECTION_PER_CLASS means the class will be persisted according to the rules
108
     * of <tt>Concrete Collection Inheritance</tt>.
109
     */
110
    const INHERITANCE_TYPE_COLLECTION_PER_CLASS = 3;
111
112
    /**
113
     * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time
114
     * by doing a property-by-property comparison with the original data. This will
115
     * be done for all entities that are in MANAGED state at commit-time.
116
     *
117
     * This is the default change tracking policy.
118
     */
119
    const CHANGETRACKING_DEFERRED_IMPLICIT = 1;
120
121
    /**
122
     * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time
123
     * by doing a property-by-property comparison with the original data. This will
124
     * be done only for entities that were explicitly saved (through persist() or a cascade).
125
     */
126
    const CHANGETRACKING_DEFERRED_EXPLICIT = 2;
127
128
    /**
129
     * NOTIFY means that Doctrine relies on the entities sending out notifications
130
     * when their properties change. Such entity classes must implement
131
     * the <tt>NotifyPropertyChanged</tt> interface.
132
     */
133
    const CHANGETRACKING_NOTIFY = 3;
134
135
    /**
136
     * SET means that fields will be written to the database using a $set operator
137
     */
138
    const STORAGE_STRATEGY_SET = 'set';
139
140
    /**
141
     * INCREMENT means that fields will be written to the database by calculating
142
     * the difference and using the $inc operator
143
     */
144
    const STORAGE_STRATEGY_INCREMENT = 'increment';
145
146
    const STORAGE_STRATEGY_PUSH_ALL = 'pushAll';
147
    const STORAGE_STRATEGY_ADD_TO_SET = 'addToSet';
148
    const STORAGE_STRATEGY_ATOMIC_SET = 'atomicSet';
149
    const STORAGE_STRATEGY_ATOMIC_SET_ARRAY = 'atomicSetArray';
150
    const STORAGE_STRATEGY_SET_ARRAY = 'setArray';
151
152
    /**
153
     * READ-ONLY: The name of the mongo database the document is mapped to.
154
     */
155
    public $db;
156
157
    /**
158
     * READ-ONLY: The name of the mongo collection the document is mapped to.
159
     */
160
    public $collection;
161
162
    /**
163
     * READ-ONLY: If the collection should be a fixed size.
164
     */
165
    public $collectionCapped;
166
167
    /**
168
     * READ-ONLY: If the collection is fixed size, its size in bytes.
169
     */
170
    public $collectionSize;
171
172
    /**
173
     * READ-ONLY: If the collection is fixed size, the maximum number of elements to store in the collection.
174
     */
175
    public $collectionMax;
176
177
    /**
178
     * READ-ONLY Describes how MongoDB clients route read operations to the members of a replica set.
179
     */
180
    public $readPreference;
181
182
    /**
183
     * READ-ONLY Associated with readPreference Allows to specify criteria so that your application can target read
184
     * operations to specific members, based on custom parameters.
185
     */
186
    public $readPreferenceTags;
187
188
    /**
189
     * READ-ONLY: Describes the level of acknowledgement requested from MongoDB for write operations.
190
     */
191
    public $writeConcern;
192
193
    /**
194
     * READ-ONLY: The field name of the document identifier.
195
     */
196
    public $identifier;
197
198
    /**
199
     * READ-ONLY: The array of indexes for the document collection.
200
     */
201
    public $indexes = array();
202
203
    /**
204
     * READ-ONLY: Keys and options describing shard key. Only for sharded collections.
205
     */
206
    public $shardKey;
207
208
    /**
209
     * READ-ONLY: The name of the document class.
210
     */
211
    public $name;
212
213
    /**
214
     * READ-ONLY: The namespace the document class is contained in.
215
     *
216
     * @var string
217
     * @todo Not really needed. Usage could be localized.
218
     */
219
    public $namespace;
220
221
    /**
222
     * READ-ONLY: The name of the document class that is at the root of the mapped document inheritance
223
     * hierarchy. If the document is not part of a mapped inheritance hierarchy this is the same
224
     * as {@link $documentName}.
225
     *
226
     * @var string
227
     */
228
    public $rootDocumentName;
229
230
    /**
231
     * The name of the custom repository class used for the document class.
232
     * (Optional).
233
     *
234
     * @var string
235
     */
236
    public $customRepositoryClassName;
237
238
    /**
239
     * READ-ONLY: The names of the parent classes (ancestors).
240
     *
241
     * @var array
242
     */
243
    public $parentClasses = array();
244
245
    /**
246
     * READ-ONLY: The names of all subclasses (descendants).
247
     *
248
     * @var array
249
     */
250
    public $subClasses = array();
251
252
    /**
253
     * The ReflectionProperty instances of the mapped class.
254
     *
255
     * @var \ReflectionProperty[]
256
     */
257
    public $reflFields = array();
258
259
    /**
260
     * READ-ONLY: The inheritance mapping type used by the class.
261
     *
262
     * @var integer
263
     */
264
    public $inheritanceType = self::INHERITANCE_TYPE_NONE;
265
266
    /**
267
     * READ-ONLY: The Id generator type used by the class.
268
     *
269
     * @var string
270
     */
271
    public $generatorType = self::GENERATOR_TYPE_AUTO;
272
273
    /**
274
     * READ-ONLY: The Id generator options.
275
     *
276
     * @var array
277
     */
278
    public $generatorOptions = array();
279
280
    /**
281
     * READ-ONLY: The ID generator used for generating IDs for this class.
282
     *
283
     * @var \Doctrine\ODM\MongoDB\Id\AbstractIdGenerator
284
     */
285
    public $idGenerator;
286
287
    /**
288
     * READ-ONLY: The field mappings of the class.
289
     * Keys are field names and values are mapping definitions.
290
     *
291
     * The mapping definition array has the following values:
292
     *
293
     * - <b>fieldName</b> (string)
294
     * The name of the field in the Document.
295
     *
296
     * - <b>id</b> (boolean, optional)
297
     * Marks the field as the primary key of the document. Multiple fields of an
298
     * document can have the id attribute, forming a composite key.
299
     *
300
     * @var array
301
     */
302
    public $fieldMappings = array();
303
304
    /**
305
     * READ-ONLY: The association mappings of the class.
306
     * Keys are field names and values are mapping definitions.
307
     *
308
     * @var array
309
     */
310
    public $associationMappings = array();
311
312
    /**
313
     * READ-ONLY: Array of fields to also load with a given method.
314
     *
315
     * @var array
316
     */
317
    public $alsoLoadMethods = array();
318
319
    /**
320
     * READ-ONLY: The registered lifecycle callbacks for documents of this class.
321
     *
322
     * @var array
323
     */
324
    public $lifecycleCallbacks = array();
325
326
    /**
327
     * READ-ONLY: The discriminator value of this class.
328
     *
329
     * <b>This does only apply to the JOINED and SINGLE_COLLECTION inheritance mapping strategies
330
     * where a discriminator field is used.</b>
331
     *
332
     * @var mixed
333
     * @see discriminatorField
334
     */
335
    public $discriminatorValue;
336
337
    /**
338
     * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
339
     *
340
     * <b>This does only apply to the SINGLE_COLLECTION inheritance mapping strategy
341
     * where a discriminator field is used.</b>
342
     *
343
     * @var mixed
344
     * @see discriminatorField
345
     */
346
    public $discriminatorMap = array();
347
348
    /**
349
     * READ-ONLY: The definition of the discriminator field used in SINGLE_COLLECTION
350
     * inheritance mapping.
351
     *
352
     * @var string
353
     */
354
    public $discriminatorField;
355
356
    /**
357
     * READ-ONLY: The default value for discriminatorField in case it's not set in the document
358
     *
359
     * @var string
360
     * @see discriminatorField
361
     */
362
    public $defaultDiscriminatorValue;
363
364
    /**
365
     * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
366
     *
367
     * @var boolean
368
     */
369
    public $isMappedSuperclass = false;
370
371
    /**
372
     * READ-ONLY: Whether this class describes the mapping of a embedded document.
373
     *
374
     * @var boolean
375
     */
376
    public $isEmbeddedDocument = false;
377
378
    /**
379
     * READ-ONLY: Whether this class describes the mapping of an aggregation result document.
380
     *
381
     * @var boolean
382
     */
383
    public $isQueryResultDocument = false;
384
385
    /**
386
     * READ-ONLY: The policy used for change-tracking on entities of this class.
387
     *
388
     * @var integer
389
     */
390
    public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;
391
392
    /**
393
     * READ-ONLY: A flag for whether or not instances of this class are to be versioned
394
     * with optimistic locking.
395
     *
396
     * @var boolean $isVersioned
397
     */
398
    public $isVersioned;
399
400
    /**
401
     * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
402
     *
403
     * @var mixed $versionField
404
     */
405
    public $versionField;
406
407
    /**
408
     * READ-ONLY: A flag for whether or not instances of this class are to allow pessimistic
409
     * locking.
410
     *
411
     * @var boolean $isLockable
412
     */
413
    public $isLockable;
414
415
    /**
416
     * READ-ONLY: The name of the field which is used for locking a document.
417
     *
418
     * @var mixed $lockField
419
     */
420
    public $lockField;
421
422
    /**
423
     * The ReflectionClass instance of the mapped class.
424
     *
425
     * @var \ReflectionClass
426
     */
427
    public $reflClass;
428
429
    /**
430
     * READ_ONLY: A flag for whether or not this document is read-only.
431
     *
432
     * @var bool
433
     */
434
    public $isReadOnly;
435
436
    /**
437
     * @var \Doctrine\Instantiator\InstantiatorInterface|null
438
     */
439
    private $instantiator;
440
441
    /**
442
     * Initializes a new ClassMetadata instance that will hold the object-document mapping
443
     * metadata of the class with the given name.
444
     *
445
     * @param string $documentName The name of the document class the new instance is used for.
446
     */
447 1474
    public function __construct($documentName)
448
    {
449 1474
        $this->name = $documentName;
450 1474
        $this->rootDocumentName = $documentName;
451 1474
        $this->reflClass = new \ReflectionClass($documentName);
452 1474
        $this->namespace = $this->reflClass->getNamespaceName();
453 1474
        $this->setCollection($this->reflClass->getShortName());
454 1474
        $this->instantiator = new Instantiator();
455 1474
    }
456
457
    /**
458
     * Helper method to get reference id of ref* type references
459
     * @param mixed  $reference
460
     * @param string $storeAs
461
     * @return mixed
462
     * @internal
463
     */
464 120
    public static function getReferenceId($reference, $storeAs)
465
    {
466 120
        return $storeAs === ClassMetadata::REFERENCE_STORE_AS_ID ? $reference : $reference[ClassMetadata::getReferencePrefix($storeAs) . 'id'];
467
    }
468
469
    /**
470
     * Returns the reference prefix used for a reference
471
     * @param string $storeAs
472
     * @return string
473
     */
474 185
    private static function getReferencePrefix($storeAs)
475
    {
476 185
        if (!in_array($storeAs, [ClassMetadata::REFERENCE_STORE_AS_REF, ClassMetadata::REFERENCE_STORE_AS_DB_REF, ClassMetadata::REFERENCE_STORE_AS_DB_REF_WITH_DB])) {
477
            throw new \LogicException('Can only get a reference prefix for DBRef and reference arrays');
478
        }
479
480 185
        return $storeAs === ClassMetadata::REFERENCE_STORE_AS_REF ? '' : '$';
481
    }
482
483
    /**
484
     * Returns a fully qualified field name for a given reference
485
     * @param string $storeAs
486
     * @param string $pathPrefix The field path prefix
487
     * @return string
488
     * @internal
489
     */
490 134
    public static function getReferenceFieldName($storeAs, $pathPrefix = '')
491
    {
492 134
        if ($storeAs === ClassMetadata::REFERENCE_STORE_AS_ID) {
493 94
            return $pathPrefix;
494
        }
495
496 122
        return ($pathPrefix ? $pathPrefix . '.' : '') . static::getReferencePrefix($storeAs) . 'id';
0 ignored issues
show
Bug introduced by
Since getReferencePrefix() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getReferencePrefix() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
497
    }
498
499
    /**
500
     * {@inheritDoc}
501
     */
502 1365
    public function getReflectionClass()
503
    {
504 1365
        if ( ! $this->reflClass) {
505
            $this->reflClass = new \ReflectionClass($this->name);
506
        }
507
508 1365
        return $this->reflClass;
509
    }
510
511
    /**
512
     * {@inheritDoc}
513
     */
514 321
    public function isIdentifier($fieldName)
515
    {
516 321
        return $this->identifier === $fieldName;
517
    }
518
519
    /**
520
     * INTERNAL:
521
     * Sets the mapped identifier field of this class.
522
     *
523
     * @param string $identifier
524
     */
525 886
    public function setIdentifier($identifier)
526
    {
527 886
        $this->identifier = $identifier;
528 886
    }
529
530
    /**
531
     * {@inheritDoc}
532
     *
533
     * Since MongoDB only allows exactly one identifier field
534
     * this will always return an array with only one value
535
     */
536 38
    public function getIdentifier()
537
    {
538 38
        return array($this->identifier);
539
    }
540
541
    /**
542
     * {@inheritDoc}
543
     *
544
     * Since MongoDB only allows exactly one identifier field
545
     * this will always return an array with only one value
546
     */
547 97
    public function getIdentifierFieldNames()
548
    {
549 97
        return array($this->identifier);
550
    }
551
552
    /**
553
     * {@inheritDoc}
554
     */
555 886
    public function hasField($fieldName)
556
    {
557 886
        return isset($this->fieldMappings[$fieldName]);
558
    }
559
560
    /**
561
     * Sets the inheritance type used by the class and it's subclasses.
562
     *
563
     * @param integer $type
564
     */
565 902
    public function setInheritanceType($type)
566
    {
567 902
        $this->inheritanceType = $type;
568 902
    }
569
570
    /**
571
     * Checks whether a mapped field is inherited from an entity superclass.
572
     *
573
     * @param  string $fieldName
574
     *
575
     * @return boolean TRUE if the field is inherited, FALSE otherwise.
576
     */
577 1361
    public function isInheritedField($fieldName)
578
    {
579 1361
        return isset($this->fieldMappings[$fieldName]['inherited']);
580
    }
581
582
    /**
583
     * Registers a custom repository class for the document class.
584
     *
585
     * @param string $repositoryClassName The class name of the custom repository.
586
     */
587 834
    public function setCustomRepositoryClass($repositoryClassName)
588
    {
589 834
        if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
590
            return;
591
        }
592
593 834 View Code Duplication
        if ($repositoryClassName && strpos($repositoryClassName, '\\') === false && strlen($this->namespace)) {
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...
594 3
            $repositoryClassName = $this->namespace . '\\' . $repositoryClassName;
595
        }
596
597 834
        $this->customRepositoryClassName = $repositoryClassName;
598 834
    }
599
600
    /**
601
     * Dispatches the lifecycle event of the given document by invoking all
602
     * registered callbacks.
603
     *
604
     * @param string $event     Lifecycle event
605
     * @param object $document  Document on which the event occurred
606
     * @param array  $arguments Arguments to pass to all callbacks
607
     * @throws \InvalidArgumentException if document class is not this class or
608
     *                                   a Proxy of this class
609
     */
610 599
    public function invokeLifecycleCallbacks($event, $document, array $arguments = null)
611
    {
612 599
        if ( ! $document instanceof $this->name) {
613 1
            throw new \InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"', $this->name, get_class($document)));
614
        }
615
616 598
        if (empty($this->lifecycleCallbacks[$event])) {
617 583
            return;
618
        }
619
620 176
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
621 176
            if ($arguments !== null) {
622 175
                call_user_func_array(array($document, $callback), $arguments);
623
            } else {
624 176
                $document->$callback();
625
            }
626
        }
627 176
    }
628
629
    /**
630
     * Checks whether the class has callbacks registered for a lifecycle event.
631
     *
632
     * @param string $event Lifecycle event
633
     *
634
     * @return boolean
635
     */
636
    public function hasLifecycleCallbacks($event)
637
    {
638
        return ! empty($this->lifecycleCallbacks[$event]);
639
    }
640
641
    /**
642
     * Gets the registered lifecycle callbacks for an event.
643
     *
644
     * @param string $event
645
     * @return array
646
     */
647
    public function getLifecycleCallbacks($event)
648
    {
649
        return $this->lifecycleCallbacks[$event] ?? array();
650
    }
651
652
    /**
653
     * Adds a lifecycle callback for documents of this class.
654
     *
655
     * If the callback is already registered, this is a NOOP.
656
     *
657
     * @param string $callback
658
     * @param string $event
659
     */
660 802
    public function addLifecycleCallback($callback, $event)
661
    {
662 802
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
663 1
            return;
664
        }
665
666 802
        $this->lifecycleCallbacks[$event][] = $callback;
667 802
    }
668
669
    /**
670
     * Sets the lifecycle callbacks for documents of this class.
671
     *
672
     * Any previously registered callbacks are overwritten.
673
     *
674
     * @param array $callbacks
675
     */
676 885
    public function setLifecycleCallbacks(array $callbacks)
677
    {
678 885
        $this->lifecycleCallbacks = $callbacks;
679 885
    }
680
681
    /**
682
     * Registers a method for loading document data before field hydration.
683
     *
684
     * Note: A method may be registered multiple times for different fields.
685
     * it will be invoked only once for the first field found.
686
     *
687
     * @param string       $method Method name
688
     * @param array|string $fields Database field name(s)
689
     */
690 14
    public function registerAlsoLoadMethod($method, $fields)
691
    {
692 14
        $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : array($fields);
693 14
    }
694
695
    /**
696
     * Sets the AlsoLoad methods for documents of this class.
697
     *
698
     * Any previously registered methods are overwritten.
699
     *
700
     * @param array $methods
701
     */
702 885
    public function setAlsoLoadMethods(array $methods)
703
    {
704 885
        $this->alsoLoadMethods = $methods;
705 885
    }
706
707
    /**
708
     * Sets the discriminator field.
709
     *
710
     * The field name is the the unmapped database field. Discriminator values
711
     * are only used to discern the hydration class and are not mapped to class
712
     * properties.
713
     *
714
     * @param string $discriminatorField
715
     *
716
     * @throws MappingException If the discriminator field conflicts with the
717
     *                          "name" attribute of a mapped field.
718
     */
719 911
    public function setDiscriminatorField($discriminatorField)
720
    {
721 911
        if ($discriminatorField === null) {
722 843
            $this->discriminatorField = null;
723
724 843
            return;
725
        }
726
727
        // Handle array argument with name/fieldName keys for BC
728 117
        if (is_array($discriminatorField)) {
729
            if (isset($discriminatorField['name'])) {
730
                $discriminatorField = $discriminatorField['name'];
731
            } elseif (isset($discriminatorField['fieldName'])) {
732
                $discriminatorField = $discriminatorField['fieldName'];
733
            }
734
        }
735
736 117
        foreach ($this->fieldMappings as $fieldMapping) {
737 4
            if ($discriminatorField == $fieldMapping['name']) {
738 4
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
739
            }
740
        }
741
742 116
        $this->discriminatorField = $discriminatorField;
743 116
    }
744
745
    /**
746
     * Sets the discriminator values used by this class.
747
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
748
     *
749
     * @param array $map
750
     *
751
     * @throws MappingException
752
     */
753 904
    public function setDiscriminatorMap(array $map)
754
    {
755 904
        foreach ($map as $value => $className) {
756 112
            if (strpos($className, '\\') === false && strlen($this->namespace)) {
757 82
                $className = $this->namespace . '\\' . $className;
758
            }
759 112
            $this->discriminatorMap[$value] = $className;
760 112
            if ($this->name == $className) {
761 104
                $this->discriminatorValue = $value;
762
            } else {
763 111
                if ( ! class_exists($className)) {
764
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
765
                }
766 111
                if (is_subclass_of($className, $this->name)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $this->name can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
767 112
                    $this->subClasses[] = $className;
768
                }
769
            }
770
        }
771 904
    }
772
773
    /**
774
     * Sets the default discriminator value to be used for this class
775
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies if the document has no discriminator value
776
     *
777
     * @param string $defaultDiscriminatorValue
778
     *
779
     * @throws MappingException
780
     */
781 888
    public function setDefaultDiscriminatorValue($defaultDiscriminatorValue)
782
    {
783 888
        if ($defaultDiscriminatorValue === null) {
784 885
            $this->defaultDiscriminatorValue = null;
785
786 885
            return;
787
        }
788
789 48
        if (!array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
790
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
791
        }
792
793 48
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
794 48
    }
795
796
    /**
797
     * Sets the discriminator value for this class.
798
     * Used for JOINED/SINGLE_TABLE inheritance and multiple document types in a single
799
     * collection.
800
     *
801
     * @param string $value
802
     */
803 3
    public function setDiscriminatorValue($value)
804
    {
805 3
        $this->discriminatorMap[$value] = $this->name;
806 3
        $this->discriminatorValue = $value;
807 3
    }
808
809
    /**
810
     * Add a index for this Document.
811
     *
812
     * @param array $keys Array of keys for the index.
813
     * @param array $options Array of options for the index.
814
     */
815 183
    public function addIndex($keys, array $options = array())
816
    {
817 183
        $this->indexes[] = array(
818 183 View Code Duplication
            'keys' => array_map(function($value) {
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...
819 183
                if ($value == 1 || $value == -1) {
820 54
                    return (int) $value;
821
                }
822 176
                if (is_string($value)) {
823 176
                    $lower = strtolower($value);
824 176
                    if ($lower === 'asc') {
825 176
                        return 1;
826
                    } elseif ($lower === 'desc') {
827
                        return -1;
828
                    }
829
                }
830
                return $value;
831 183
            }, $keys),
832 183
            'options' => $options
833
        );
834 183
    }
835
836
    /**
837
     * Returns the array of indexes for this Document.
838
     *
839
     * @return array $indexes The array of indexes.
840
     */
841 23
    public function getIndexes()
842
    {
843 23
        return $this->indexes;
844
    }
845
846
    /**
847
     * Checks whether this document has indexes or not.
848
     *
849
     * @return boolean
850
     */
851
    public function hasIndexes()
852
    {
853
        return $this->indexes ? true : false;
854
    }
855
856
    /**
857
     * Set shard key for this Document.
858
     *
859
     * @param array $keys Array of document keys.
860
     * @param array $options Array of sharding options.
861
     *
862
     * @throws MappingException
863
     */
864 71
    public function setShardKey(array $keys, array $options = array())
865
    {
866 71
        if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && !is_null($this->shardKey)) {
867 2
            throw MappingException::shardKeyInSingleCollInheritanceSubclass($this->getName());
868
        }
869
870 71
        if ($this->isEmbeddedDocument) {
871 2
            throw MappingException::embeddedDocumentCantHaveShardKey($this->getName());
872
        }
873
874 69
        foreach (array_keys($keys) as $field) {
875 69
            if (! isset($this->fieldMappings[$field])) {
876 62
                continue;
877
            }
878
879 7
            if (in_array($this->fieldMappings[$field]['type'], ['many', 'collection'])) {
880 3
                throw MappingException::noMultiKeyShardKeys($this->getName(), $field);
881
            }
882
883 4
            if ($this->fieldMappings[$field]['strategy'] !== static::STORAGE_STRATEGY_SET) {
884 4
                throw MappingException::onlySetStrategyAllowedInShardKey($this->getName(), $field);
885
            }
886
        }
887
888 65
        $this->shardKey = array(
889 65 View Code Duplication
            'keys' => array_map(function($value) {
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...
890 65
                if ($value == 1 || $value == -1) {
891 6
                    return (int) $value;
892
                }
893 64
                if (is_string($value)) {
894 64
                    $lower = strtolower($value);
895 64
                    if ($lower === 'asc') {
896 63
                        return 1;
897 46
                    } elseif ($lower === 'desc') {
898
                        return -1;
899
                    }
900
                }
901 46
                return $value;
902 65
            }, $keys),
903 65
            'options' => $options
904
        );
905 65
    }
906
907
    /**
908
     * @return array
909
     */
910 17
    public function getShardKey()
911
    {
912 17
        return $this->shardKey;
913
    }
914
915
    /**
916
     * Checks whether this document has shard key or not.
917
     *
918
     * @return bool
919
     */
920 1096
    public function isSharded()
921
    {
922 1096
        return $this->shardKey ? true : false;
923
    }
924
925
    /**
926
     * Sets the read preference used by this class.
927
     *
928
     * @param string $readPreference
929
     * @param array|null $tags
930
     */
931 885
    public function setReadPreference($readPreference, $tags)
932
    {
933 885
        $this->readPreference = $readPreference;
934 885
        $this->readPreferenceTags = $tags;
935 885
    }
936
937
    /**
938
     * Sets the write concern used by this class.
939
     *
940
     * @param string $writeConcern
941
     */
942 895
    public function setWriteConcern($writeConcern)
943
    {
944 895
        $this->writeConcern = $writeConcern;
945 895
    }
946
947
    /**
948
     * @return string
949
     */
950 11
    public function getWriteConcern()
951
    {
952 11
        return $this->writeConcern;
953
    }
954
955
    /**
956
     * Whether there is a write concern configured for this class.
957
     *
958
     * @return bool
959
     */
960 548
    public function hasWriteConcern()
961
    {
962 548
        return $this->writeConcern !== null;
963
    }
964
965
    /**
966
     * Sets the change tracking policy used by this class.
967
     *
968
     * @param integer $policy
969
     */
970 887
    public function setChangeTrackingPolicy($policy)
971
    {
972 887
        $this->changeTrackingPolicy = $policy;
973 887
    }
974
975
    /**
976
     * Whether the change tracking policy of this class is "deferred explicit".
977
     *
978
     * @return boolean
979
     */
980 61
    public function isChangeTrackingDeferredExplicit()
981
    {
982 61
        return $this->changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_EXPLICIT;
983
    }
984
985
    /**
986
     * Whether the change tracking policy of this class is "deferred implicit".
987
     *
988
     * @return boolean
989
     */
990 568
    public function isChangeTrackingDeferredImplicit()
991
    {
992 568
        return $this->changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_IMPLICIT;
993
    }
994
995
    /**
996
     * Whether the change tracking policy of this class is "notify".
997
     *
998
     * @return boolean
999
     */
1000 308
    public function isChangeTrackingNotify()
1001
    {
1002 308
        return $this->changeTrackingPolicy == self::CHANGETRACKING_NOTIFY;
1003
    }
1004
1005
    /**
1006
     * Gets the ReflectionProperties of the mapped class.
1007
     *
1008
     * @return array An array of ReflectionProperty instances.
1009
     */
1010 97
    public function getReflectionProperties()
1011
    {
1012 97
        return $this->reflFields;
1013
    }
1014
1015
    /**
1016
     * Gets a ReflectionProperty for a specific field of the mapped class.
1017
     *
1018
     * @param string $name
1019
     *
1020
     * @return \ReflectionProperty
1021
     */
1022
    public function getReflectionProperty($name)
1023
    {
1024
        return $this->reflFields[$name];
1025
    }
1026
1027
    /**
1028
     * {@inheritDoc}
1029
     */
1030 1370
    public function getName()
1031
    {
1032 1370
        return $this->name;
1033
    }
1034
1035
    /**
1036
     * The namespace this Document class belongs to.
1037
     *
1038
     * @return string $namespace The namespace name.
1039
     */
1040
    public function getNamespace()
1041
    {
1042
        return $this->namespace;
1043
    }
1044
1045
    /**
1046
     * Returns the database this Document is mapped to.
1047
     *
1048
     * @return string $db The database name.
1049
     */
1050 1294
    public function getDatabase()
1051
    {
1052 1294
        return $this->db;
1053
    }
1054
1055
    /**
1056
     * Set the database this Document is mapped to.
1057
     *
1058
     * @param string $db The database name
1059
     */
1060 92
    public function setDatabase($db)
1061
    {
1062 92
        $this->db = $db;
1063 92
    }
1064
1065
    /**
1066
     * Get the collection this Document is mapped to.
1067
     *
1068
     * @return string $collection The collection name.
1069
     */
1070 1295
    public function getCollection()
1071
    {
1072 1295
        return $this->collection;
1073
    }
1074
1075
    /**
1076
     * Sets the collection this Document is mapped to.
1077
     *
1078
     * @param array|string $name
1079
     *
1080
     * @throws \InvalidArgumentException
1081
     */
1082 1474
    public function setCollection($name)
1083
    {
1084 1474
        if (is_array($name)) {
1085
            if ( ! isset($name['name'])) {
1086
                throw new \InvalidArgumentException('A name key is required when passing an array to setCollection()');
1087
            }
1088
            $this->collectionCapped = $name['capped'] ?? false;
1089
            $this->collectionSize = $name['size'] ?? 0;
1090
            $this->collectionMax = $name['max'] ?? 0;
1091
            $this->collection = $name['name'];
1092
        } else {
1093 1474
            $this->collection = $name;
1094
        }
1095 1474
    }
1096
1097
    /**
1098
     * Get whether or not the documents collection is capped.
1099
     *
1100
     * @return boolean
1101
     */
1102 4
    public function getCollectionCapped()
1103
    {
1104 4
        return $this->collectionCapped;
1105
    }
1106
1107
    /**
1108
     * Set whether or not the documents collection is capped.
1109
     *
1110
     * @param boolean $bool
1111
     */
1112 1
    public function setCollectionCapped($bool)
1113
    {
1114 1
        $this->collectionCapped = $bool;
1115 1
    }
1116
1117
    /**
1118
     * Get the collection size
1119
     *
1120
     * @return integer
1121
     */
1122 4
    public function getCollectionSize()
1123
    {
1124 4
        return $this->collectionSize;
1125
    }
1126
1127
    /**
1128
     * Set the collection size.
1129
     *
1130
     * @param integer $size
1131
     */
1132 1
    public function setCollectionSize($size)
1133
    {
1134 1
        $this->collectionSize = $size;
1135 1
    }
1136
1137
    /**
1138
     * Get the collection max.
1139
     *
1140
     * @return integer
1141
     */
1142 4
    public function getCollectionMax()
1143
    {
1144 4
        return $this->collectionMax;
1145
    }
1146
1147
    /**
1148
     * Set the collection max.
1149
     *
1150
     * @param integer $max
1151
     */
1152 1
    public function setCollectionMax($max)
1153
    {
1154 1
        $this->collectionMax = $max;
1155 1
    }
1156
1157
    /**
1158
     * Returns TRUE if this Document is mapped to a collection FALSE otherwise.
1159
     *
1160
     * @return boolean
1161
     */
1162
    public function isMappedToCollection()
1163
    {
1164
        return $this->collection ? true : false;
1165
    }
1166
1167
    /**
1168
     * Validates the storage strategy of a mapping for consistency
1169
     * @param array $mapping
1170
     * @throws \Doctrine\ODM\MongoDB\Mapping\MappingException
1171
     */
1172 1387
    private function applyStorageStrategy(array &$mapping)
1173
    {
1174 1387
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1175 1369
            return;
1176
        }
1177
1178
        switch (true) {
1179 1351
            case $mapping['type'] == 'int':
1180 1350
            case $mapping['type'] == 'float':
1181 818
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1182 818
                $allowedStrategies = [self::STORAGE_STRATEGY_SET, self::STORAGE_STRATEGY_INCREMENT];
1183 818
                break;
1184
1185 1350
            case $mapping['type'] == 'many':
1186 1075
                $defaultStrategy = CollectionHelper::DEFAULT_STRATEGY;
1187
                $allowedStrategies = [
1188 1075
                    self::STORAGE_STRATEGY_PUSH_ALL,
1189 1075
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1190 1075
                    self::STORAGE_STRATEGY_SET,
1191 1075
                    self::STORAGE_STRATEGY_SET_ARRAY,
1192 1075
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1193 1075
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1194
                ];
1195 1075
                break;
1196
1197
            default:
1198 1338
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1199 1338
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1200
        }
1201
1202 1351
        if (! isset($mapping['strategy'])) {
1203 1342
            $mapping['strategy'] = $defaultStrategy;
1204
        }
1205
1206 1351
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1207
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1208
        }
1209
1210 1351
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1211 1351
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1212 1
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1213
        }
1214 1350
    }
1215
1216
    /**
1217
     * Map a single embedded document.
1218
     *
1219
     * @param array $mapping The mapping information.
1220
     */
1221 6
    public function mapOneEmbedded(array $mapping)
1222
    {
1223 6
        $mapping['embedded'] = true;
1224 6
        $mapping['type'] = 'one';
1225 6
        $this->mapField($mapping);
1226 5
    }
1227
1228
    /**
1229
     * Map a collection of embedded documents.
1230
     *
1231
     * @param array $mapping The mapping information.
1232
     */
1233 5
    public function mapManyEmbedded(array $mapping)
1234
    {
1235 5
        $mapping['embedded'] = true;
1236 5
        $mapping['type'] = 'many';
1237 5
        $this->mapField($mapping);
1238 5
    }
1239
1240
    /**
1241
     * Map a single document reference.
1242
     *
1243
     * @param array $mapping The mapping information.
1244
     */
1245 2
    public function mapOneReference(array $mapping)
1246
    {
1247 2
        $mapping['reference'] = true;
1248 2
        $mapping['type'] = 'one';
1249 2
        $this->mapField($mapping);
1250 2
    }
1251
1252
    /**
1253
     * Map a collection of document references.
1254
     *
1255
     * @param array $mapping The mapping information.
1256
     */
1257 1
    public function mapManyReference(array $mapping)
1258
    {
1259 1
        $mapping['reference'] = true;
1260 1
        $mapping['type'] = 'many';
1261 1
        $this->mapField($mapping);
1262 1
    }
1263
1264
    /**
1265
     * INTERNAL:
1266
     * Adds a field mapping without completing/validating it.
1267
     * This is mainly used to add inherited field mappings to derived classes.
1268
     *
1269
     * @param array $fieldMapping
1270
     */
1271 116
    public function addInheritedFieldMapping(array $fieldMapping)
1272
    {
1273 116
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1274
1275 116
        if (isset($fieldMapping['association'])) {
1276 67
            $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1277
        }
1278 116
    }
1279
1280
    /**
1281
     * INTERNAL:
1282
     * Adds an association mapping without completing/validating it.
1283
     * This is mainly used to add inherited association mappings to derived classes.
1284
     *
1285
     * @param array $mapping
1286
     *
1287
     * @return void
1288
     *
1289
     * @throws MappingException
1290
     */
1291 68
    public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
1292
    {
1293 68
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1294 68
    }
1295
1296
    /**
1297
     * Checks whether the class has a mapped association with the given field name.
1298
     *
1299
     * @param string $fieldName
1300
     * @return boolean
1301
     */
1302 32
    public function hasReference($fieldName)
1303
    {
1304 32
        return isset($this->fieldMappings[$fieldName]['reference']);
1305
    }
1306
1307
    /**
1308
     * Checks whether the class has a mapped embed with the given field name.
1309
     *
1310
     * @param string $fieldName
1311
     * @return boolean
1312
     */
1313 5
    public function hasEmbed($fieldName)
1314
    {
1315 5
        return isset($this->fieldMappings[$fieldName]['embedded']);
1316
    }
1317
1318
    /**
1319
     * {@inheritDoc}
1320
     *
1321
     * Checks whether the class has a mapped association (embed or reference) with the given field name.
1322
     */
1323 7
    public function hasAssociation($fieldName)
1324
    {
1325 7
        return $this->hasReference($fieldName) || $this->hasEmbed($fieldName);
1326
    }
1327
1328
    /**
1329
     * {@inheritDoc}
1330
     *
1331
     * Checks whether the class has a mapped reference or embed for the specified field and
1332
     * is a single valued association.
1333
     */
1334
    public function isSingleValuedAssociation($fieldName)
1335
    {
1336
        return $this->isSingleValuedReference($fieldName) || $this->isSingleValuedEmbed($fieldName);
1337
    }
1338
1339
    /**
1340
     * {@inheritDoc}
1341
     *
1342
     * Checks whether the class has a mapped reference or embed for the specified field and
1343
     * is a collection valued association.
1344
     */
1345
    public function isCollectionValuedAssociation($fieldName)
1346
    {
1347
        return $this->isCollectionValuedReference($fieldName) || $this->isCollectionValuedEmbed($fieldName);
1348
    }
1349
1350
    /**
1351
     * Checks whether the class has a mapped association for the specified field
1352
     * and if yes, checks whether it is a single-valued association (to-one).
1353
     *
1354
     * @param string $fieldName
1355
     * @return boolean TRUE if the association exists and is single-valued, FALSE otherwise.
1356
     */
1357
    public function isSingleValuedReference($fieldName)
1358
    {
1359
        return isset($this->fieldMappings[$fieldName]['association']) &&
1360
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_ONE;
1361
    }
1362
1363
    /**
1364
     * Checks whether the class has a mapped association for the specified field
1365
     * and if yes, checks whether it is a collection-valued association (to-many).
1366
     *
1367
     * @param string $fieldName
1368
     * @return boolean TRUE if the association exists and is collection-valued, FALSE otherwise.
1369
     */
1370
    public function isCollectionValuedReference($fieldName)
1371
    {
1372
        return isset($this->fieldMappings[$fieldName]['association']) &&
1373
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_MANY;
1374
    }
1375
1376
    /**
1377
     * Checks whether the class has a mapped embedded document for the specified field
1378
     * and if yes, checks whether it is a single-valued association (to-one).
1379
     *
1380
     * @param string $fieldName
1381
     * @return boolean TRUE if the association exists and is single-valued, FALSE otherwise.
1382
     */
1383
    public function isSingleValuedEmbed($fieldName)
1384
    {
1385
        return isset($this->fieldMappings[$fieldName]['association']) &&
1386
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_ONE;
1387
    }
1388
1389
    /**
1390
     * Checks whether the class has a mapped embedded document for the specified field
1391
     * and if yes, checks whether it is a collection-valued association (to-many).
1392
     *
1393
     * @param string $fieldName
1394
     * @return boolean TRUE if the association exists and is collection-valued, FALSE otherwise.
1395
     */
1396
    public function isCollectionValuedEmbed($fieldName)
1397
    {
1398
        return isset($this->fieldMappings[$fieldName]['association']) &&
1399
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_MANY;
1400
    }
1401
1402
    /**
1403
     * Sets the ID generator used to generate IDs for instances of this class.
1404
     *
1405
     * @param \Doctrine\ODM\MongoDB\Id\AbstractIdGenerator $generator
1406
     */
1407 1305
    public function setIdGenerator($generator)
1408
    {
1409 1305
        $this->idGenerator = $generator;
1410 1305
    }
1411
1412
    /**
1413
     * Casts the identifier to its portable PHP type.
1414
     *
1415
     * @param mixed $id
1416
     * @return mixed $id
1417
     */
1418 590
    public function getPHPIdentifierValue($id)
1419
    {
1420 590
        $idType = $this->fieldMappings[$this->identifier]['type'];
1421 590
        return Type::getType($idType)->convertToPHPValue($id);
1422
    }
1423
1424
    /**
1425
     * Casts the identifier to its database type.
1426
     *
1427
     * @param mixed $id
1428
     * @return mixed $id
1429
     */
1430 655
    public function getDatabaseIdentifierValue($id)
1431
    {
1432 655
        $idType = $this->fieldMappings[$this->identifier]['type'];
1433 655
        return Type::getType($idType)->convertToDatabaseValue($id);
1434
    }
1435
1436
    /**
1437
     * Sets the document identifier of a document.
1438
     *
1439
     * The value will be converted to a PHP type before being set.
1440
     *
1441
     * @param object $document
1442
     * @param mixed $id
1443
     */
1444 519
    public function setIdentifierValue($document, $id)
1445
    {
1446 519
        $id = $this->getPHPIdentifierValue($id);
1447 519
        $this->reflFields[$this->identifier]->setValue($document, $id);
1448 519
    }
1449
1450
    /**
1451
     * Gets the document identifier as a PHP type.
1452
     *
1453
     * @param object $document
1454
     * @return mixed $id
1455
     */
1456 603
    public function getIdentifierValue($document)
1457
    {
1458 603
        return $this->reflFields[$this->identifier]->getValue($document);
1459
    }
1460
1461
    /**
1462
     * {@inheritDoc}
1463
     *
1464
     * Since MongoDB only allows exactly one identifier field this is a proxy
1465
     * to {@see getIdentifierValue()} and returns an array with the identifier
1466
     * field as a key.
1467
     */
1468
    public function getIdentifierValues($object)
1469
    {
1470
        return array($this->identifier => $this->getIdentifierValue($object));
1471
    }
1472
1473
    /**
1474
     * Get the document identifier object as a database type.
1475
     *
1476
     * @param object $document
1477
     *
1478
     * @return \MongoDB\BSON\ObjectId $id The ObjectId
1479
     */
1480 31
    public function getIdentifierObject($document)
1481
    {
1482 31
        return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));
1483
    }
1484
1485
    /**
1486
     * Sets the specified field to the specified value on the given document.
1487
     *
1488
     * @param object $document
1489
     * @param string $field
1490
     * @param mixed $value
1491
     */
1492 8
    public function setFieldValue($document, $field, $value)
1493
    {
1494 8
        if ($document instanceof Proxy && ! $document->__isInitialized()) {
1495
            //property changes to an uninitialized proxy will not be tracked or persisted,
1496
            //so the proxy needs to be loaded first.
1497 1
            $document->__load();
1498
        }
1499
1500 8
        $this->reflFields[$field]->setValue($document, $value);
1501 8
    }
1502
1503
    /**
1504
     * Gets the specified field's value off the given document.
1505
     *
1506
     * @param object $document
1507
     * @param string $field
1508
     *
1509
     * @return mixed
1510
     */
1511 27
    public function getFieldValue($document, $field)
1512
    {
1513 27
        if ($document instanceof Proxy && $field !== $this->identifier && ! $document->__isInitialized()) {
1514 1
            $document->__load();
1515
        }
1516
1517 27
        return $this->reflFields[$field]->getValue($document);
1518
    }
1519
1520
    /**
1521
     * Gets the mapping of a field.
1522
     *
1523
     * @param string $fieldName  The field name.
1524
     *
1525
     * @return array  The field mapping.
1526
     *
1527
     * @throws MappingException if the $fieldName is not found in the fieldMappings array
1528
     */
1529 167
    public function getFieldMapping($fieldName)
1530
    {
1531 167
        if ( ! isset($this->fieldMappings[$fieldName])) {
1532 6
            throw MappingException::mappingNotFound($this->name, $fieldName);
1533
        }
1534 165
        return $this->fieldMappings[$fieldName];
1535
    }
1536
1537
    /**
1538
     * Gets mappings of fields holding embedded document(s).
1539
     *
1540
     * @return array of field mappings
1541
     */
1542 559
    public function getEmbeddedFieldsMappings()
1543
    {
1544 559
        return array_filter(
1545 559
            $this->associationMappings,
1546
            function($assoc) { return ! empty($assoc['embedded']); }
1547
        );
1548
    }
1549
1550
    /**
1551
     * Gets the field mapping by its DB name.
1552
     * E.g. it returns identifier's mapping when called with _id.
1553
     *
1554
     * @param string $dbFieldName
1555
     *
1556
     * @return array
1557
     * @throws MappingException
1558
     */
1559 4
    public function getFieldMappingByDbFieldName($dbFieldName)
1560
    {
1561 4
        foreach ($this->fieldMappings as $mapping) {
1562 4
            if ($mapping['name'] == $dbFieldName) {
1563 4
                return $mapping;
1564
            }
1565
        }
1566
1567
        throw MappingException::mappingNotFoundByDbName($this->name, $dbFieldName);
1568
    }
1569
1570
    /**
1571
     * Check if the field is not null.
1572
     *
1573
     * @param string $fieldName  The field name
1574
     *
1575
     * @return boolean  TRUE if the field is not null, FALSE otherwise.
1576
     */
1577 1
    public function isNullable($fieldName)
1578
    {
1579 1
        $mapping = $this->getFieldMapping($fieldName);
1580 1
        if ($mapping !== false) {
1581 1
            return isset($mapping['nullable']) && $mapping['nullable'] == true;
1582
        }
1583
        return false;
1584
    }
1585
1586
    /**
1587
     * Checks whether the document has a discriminator field and value configured.
1588
     *
1589
     * @return boolean
1590
     */
1591 492
    public function hasDiscriminator()
1592
    {
1593 492
        return isset($this->discriminatorField, $this->discriminatorValue);
1594
    }
1595
1596
    /**
1597
     * Sets the type of Id generator to use for the mapped class.
1598
     *
1599
     * @param string $generatorType Generator type.
1600
     */
1601 885
    public function setIdGeneratorType($generatorType)
1602
    {
1603 885
        $this->generatorType = $generatorType;
1604 885
    }
1605
1606
    /**
1607
     * Sets the Id generator options.
1608
     *
1609
     * @param array $generatorOptions Generator options.
1610
     */
1611
    public function setIdGeneratorOptions($generatorOptions)
1612
    {
1613
        $this->generatorOptions = $generatorOptions;
1614
    }
1615
1616
    /**
1617
     * @return boolean
1618
     */
1619 566
    public function isInheritanceTypeNone()
1620
    {
1621 566
        return $this->inheritanceType == self::INHERITANCE_TYPE_NONE;
1622
    }
1623
1624
    /**
1625
     * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.
1626
     *
1627
     * @return boolean
1628
     */
1629 884
    public function isInheritanceTypeSingleCollection()
1630
    {
1631 884
        return $this->inheritanceType == self::INHERITANCE_TYPE_SINGLE_COLLECTION;
1632
    }
1633
1634
    /**
1635
     * Checks whether the mapped class uses the COLLECTION_PER_CLASS inheritance mapping strategy.
1636
     *
1637
     * @return boolean
1638
     */
1639
    public function isInheritanceTypeCollectionPerClass()
1640
    {
1641
        return $this->inheritanceType == self::INHERITANCE_TYPE_COLLECTION_PER_CLASS;
1642
    }
1643
1644
    /**
1645
     * Sets the mapped subclasses of this class.
1646
     *
1647
     * @param string[] $subclasses The names of all mapped subclasses.
1648
     */
1649 2
    public function setSubclasses(array $subclasses)
1650
    {
1651 2
        foreach ($subclasses as $subclass) {
1652 2
            if (strpos($subclass, '\\') === false && strlen($this->namespace)) {
1653 1
                $this->subClasses[] = $this->namespace . '\\' . $subclass;
1654
            } else {
1655 2
                $this->subClasses[] = $subclass;
1656
            }
1657
        }
1658 2
    }
1659
1660
    /**
1661
     * Sets the parent class names.
1662
     * Assumes that the class names in the passed array are in the order:
1663
     * directParent -> directParentParent -> directParentParentParent ... -> root.
1664
     *
1665
     * @param string[] $classNames
1666
     */
1667 1361
    public function setParentClasses(array $classNames)
1668
    {
1669 1361
        $this->parentClasses = $classNames;
1670
1671 1361
        if (count($classNames) > 0) {
1672 100
            $this->rootDocumentName = array_pop($classNames);
1673
        }
1674 1361
    }
1675
1676
    /**
1677
     * Checks whether the class will generate a new \MongoDB\BSON\ObjectId instance for us.
1678
     *
1679
     * @return boolean TRUE if the class uses the AUTO generator, FALSE otherwise.
1680
     */
1681
    public function isIdGeneratorAuto()
1682
    {
1683
        return $this->generatorType == self::GENERATOR_TYPE_AUTO;
1684
    }
1685
1686
    /**
1687
     * Checks whether the class will use a collection to generate incremented identifiers.
1688
     *
1689
     * @return boolean TRUE if the class uses the INCREMENT generator, FALSE otherwise.
1690
     */
1691
    public function isIdGeneratorIncrement()
1692
    {
1693
        return $this->generatorType == self::GENERATOR_TYPE_INCREMENT;
1694
    }
1695
1696
    /**
1697
     * Checks whether the class will generate a uuid id.
1698
     *
1699
     * @return boolean TRUE if the class uses the UUID generator, FALSE otherwise.
1700
     */
1701
    public function isIdGeneratorUuid()
1702
    {
1703
        return $this->generatorType == self::GENERATOR_TYPE_UUID;
1704
    }
1705
1706
    /**
1707
     * Checks whether the class uses no id generator.
1708
     *
1709
     * @return boolean TRUE if the class does not use any id generator, FALSE otherwise.
1710
     */
1711
    public function isIdGeneratorNone()
1712
    {
1713
        return $this->generatorType == self::GENERATOR_TYPE_NONE;
1714
    }
1715
1716
    /**
1717
     * Sets the version field mapping used for versioning. Sets the default
1718
     * value to use depending on the column type.
1719
     *
1720
     * @param array $mapping   The version field mapping array
1721
     *
1722
     * @throws LockException
1723
     */
1724 67
    public function setVersionMapping(array &$mapping)
1725
    {
1726 67
        if ($mapping['type'] !== 'int' && $mapping['type'] !== 'date') {
1727 1
            throw LockException::invalidVersionFieldType($mapping['type']);
1728
        }
1729
1730 66
        $this->isVersioned  = true;
1731 66
        $this->versionField = $mapping['fieldName'];
1732 66
    }
1733
1734
    /**
1735
     * Sets whether this class is to be versioned for optimistic locking.
1736
     *
1737
     * @param boolean $bool
1738
     */
1739 885
    public function setVersioned($bool)
1740
    {
1741 885
        $this->isVersioned = $bool;
1742 885
    }
1743
1744
    /**
1745
     * Sets the name of the field that is to be used for versioning if this class is
1746
     * versioned for optimistic locking.
1747
     *
1748
     * @param string $versionField
1749
     */
1750 885
    public function setVersionField($versionField)
1751
    {
1752 885
        $this->versionField = $versionField;
1753 885
    }
1754
1755
    /**
1756
     * Sets the version field mapping used for versioning. Sets the default
1757
     * value to use depending on the column type.
1758
     *
1759
     * @param array $mapping   The version field mapping array
1760
     *
1761
     * @throws \Doctrine\ODM\MongoDB\LockException
1762
     */
1763 22
    public function setLockMapping(array &$mapping)
1764
    {
1765 22
        if ($mapping['type'] !== 'int') {
1766 1
            throw LockException::invalidLockFieldType($mapping['type']);
1767
        }
1768
1769 21
        $this->isLockable = true;
1770 21
        $this->lockField = $mapping['fieldName'];
1771 21
    }
1772
1773
    /**
1774
     * Sets whether this class is to allow pessimistic locking.
1775
     *
1776
     * @param boolean $bool
1777
     */
1778
    public function setLockable($bool)
1779
    {
1780
        $this->isLockable = $bool;
1781
    }
1782
1783
    /**
1784
     * Sets the name of the field that is to be used for storing whether a document
1785
     * is currently locked or not.
1786
     *
1787
     * @param string $lockField
1788
     */
1789
    public function setLockField($lockField)
1790
    {
1791
        $this->lockField = $lockField;
1792
    }
1793
1794
    /**
1795
     * Marks this class as read only, no change tracking is applied to it.
1796
     */
1797 5
    public function markReadOnly()
1798
    {
1799 5
        $this->isReadOnly = true;
1800 5
    }
1801
1802
    /**
1803
     * {@inheritDoc}
1804
     */
1805
    public function getFieldNames()
1806
    {
1807
        return array_keys($this->fieldMappings);
1808
    }
1809
1810
    /**
1811
     * {@inheritDoc}
1812
     */
1813
    public function getAssociationNames()
1814
    {
1815
        return array_keys($this->associationMappings);
1816
    }
1817
1818
    /**
1819
     * {@inheritDoc}
1820
     */
1821 23
    public function getTypeOfField($fieldName)
1822
    {
1823 23
        return isset($this->fieldMappings[$fieldName]) ?
1824 23
            $this->fieldMappings[$fieldName]['type'] : null;
1825
    }
1826
1827
    /**
1828
     * {@inheritDoc}
1829
     */
1830 4
    public function getAssociationTargetClass($assocName)
1831
    {
1832 4
        if ( ! isset($this->associationMappings[$assocName])) {
1833 2
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1834
        }
1835
1836 2
        return $this->associationMappings[$assocName]['targetDocument'];
1837
    }
1838
1839
    /**
1840
     * Retrieve the collectionClass associated with an association
1841
     *
1842
     * @param string $assocName
1843
     */
1844 1
    public function getAssociationCollectionClass($assocName)
1845
    {
1846 1
        if ( ! isset($this->associationMappings[$assocName])) {
1847
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1848
        }
1849
1850 1
        if ( ! array_key_exists('collectionClass', $this->associationMappings[$assocName])) {
1851
            throw new InvalidArgumentException("collectionClass can only be applied to 'embedMany' and 'referenceMany' associations.");
1852
        }
1853
1854 1
        return $this->associationMappings[$assocName]['collectionClass'];
1855
    }
1856
1857
    /**
1858
     * {@inheritDoc}
1859
     */
1860
    public function isAssociationInverseSide($fieldName)
1861
    {
1862
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1863
    }
1864
1865
    /**
1866
     * {@inheritDoc}
1867
     */
1868
    public function getAssociationMappedByTargetField($fieldName)
1869
    {
1870
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1871
    }
1872
1873
    /**
1874
     * Map a field.
1875
     *
1876
     * @param array $mapping The mapping information.
1877
     *
1878
     * @return array
1879
     *
1880
     * @throws MappingException
1881
     */
1882 1399
    public function mapField(array $mapping)
1883
    {
1884 1399
        if ( ! isset($mapping['fieldName']) && isset($mapping['name'])) {
1885 8
            $mapping['fieldName'] = $mapping['name'];
1886
        }
1887 1399
        if ( ! isset($mapping['fieldName'])) {
1888
            throw MappingException::missingFieldName($this->name);
1889
        }
1890 1399
        if ( ! isset($mapping['name'])) {
1891 1390
            $mapping['name'] = $mapping['fieldName'];
1892
        }
1893 1399
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1894 1
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, $mapping['name']);
1895
        }
1896 1398
        if (isset($this->fieldMappings[$mapping['fieldName']])) {
1897
            //throw MappingException::duplicateFieldMapping($this->name, $mapping['fieldName']);
1898
        }
1899 1398
        if ($this->discriminatorField !== null && $this->discriminatorField == $mapping['name']) {
1900 1
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1901
        }
1902 1397 View Code Duplication
        if (isset($mapping['targetDocument']) && strpos($mapping['targetDocument'], '\\') === false && strlen($this->namespace)) {
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...
1903 1103
            $mapping['targetDocument'] = $this->namespace . '\\' . $mapping['targetDocument'];
1904
        }
1905 1397
        if (isset($mapping['collectionClass'])) {
1906 53 View Code Duplication
            if (strpos($mapping['collectionClass'], '\\') === false && strlen($this->namespace)) {
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...
1907 51
                $mapping['collectionClass'] = $this->namespace . '\\' . $mapping['collectionClass'];
1908
            }
1909 53
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1910
        }
1911 1397
        if ( ! empty($mapping['collectionClass'])) {
1912 53
            $rColl = new \ReflectionClass($mapping['collectionClass']);
1913 53
            if ( ! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1914 1
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1915
            }
1916
        }
1917
1918 1396
        if (isset($mapping['discriminatorMap'])) {
1919 114
            foreach ($mapping['discriminatorMap'] as $key => $class) {
1920 114 View Code Duplication
                if (strpos($class, '\\') === false && strlen($this->namespace)) {
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...
1921 114
                    $mapping['discriminatorMap'][$key] = $this->namespace . '\\' . $class;
1922
                }
1923
            }
1924
        }
1925
1926 1396
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1927 1
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1928
        }
1929
1930 1395
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : array();
1931
1932 1395
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
1933 1105
            $cascades = array('remove', 'persist', 'refresh', 'merge', 'detach');
1934
        }
1935
1936 1395
        if (isset($mapping['embedded'])) {
1937 1065
            unset($mapping['cascade']);
1938 1390
        } elseif (isset($mapping['cascade'])) {
1939 904
            $mapping['cascade'] = $cascades;
1940
        }
1941
1942 1395
        $mapping['isCascadeRemove'] = in_array('remove', $cascades);
1943 1395
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1944 1395
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1945 1395
        $mapping['isCascadeMerge'] = in_array('merge', $cascades);
1946 1395
        $mapping['isCascadeDetach'] = in_array('detach', $cascades);
1947
1948 1395
        if (isset($mapping['id']) && $mapping['id'] === true) {
1949 1367
            $mapping['name'] = '_id';
1950 1367
            $this->identifier = $mapping['fieldName'];
1951 1367 View Code Duplication
            if (isset($mapping['strategy'])) {
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...
1952 1361
                $this->generatorType = constant(ClassMetadata::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
1953
            }
1954 1367
            $this->generatorOptions = $mapping['options'] ?? array();
1955 1367
            switch ($this->generatorType) {
1956 1367
                case self::GENERATOR_TYPE_AUTO:
1957 1294
                    $mapping['type'] = 'id';
1958 1294
                    break;
1959
                default:
1960 146
                    if ( ! empty($this->generatorOptions['type'])) {
1961 56
                        $mapping['type'] = $this->generatorOptions['type'];
1962 90
                    } elseif (empty($mapping['type'])) {
1963 77
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
1964
                    }
1965
            }
1966 1367
            unset($this->generatorOptions['type']);
1967
        }
1968
1969 1395
        if ( ! isset($mapping['nullable'])) {
1970 35
            $mapping['nullable'] = false;
1971
        }
1972
1973 1395
        if (isset($mapping['reference'])
1974 1395
            && isset($mapping['storeAs'])
1975 1395
            && $mapping['storeAs'] === ClassMetadata::REFERENCE_STORE_AS_ID
1976 1395
            && ! isset($mapping['targetDocument'])
1977
        ) {
1978 3
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
1979
        }
1980
1981 1392
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
1982 1392
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
1983 4
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
1984
        }
1985
1986 1388
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
1987 1
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
1988
        }
1989
1990 1387 View Code Duplication
        if (isset($mapping['reference']) && $mapping['type'] === 'one') {
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...
1991 997
            $mapping['association'] = self::REFERENCE_ONE;
1992
        }
1993 1387 View Code Duplication
        if (isset($mapping['reference']) && $mapping['type'] === 'many') {
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...
1994 950
            $mapping['association'] = self::REFERENCE_MANY;
1995
        }
1996 1387 View Code Duplication
        if (isset($mapping['embedded']) && $mapping['type'] === 'one') {
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...
1997 942
            $mapping['association'] = self::EMBED_ONE;
1998
        }
1999 1387 View Code Duplication
        if (isset($mapping['embedded']) && $mapping['type'] === 'many') {
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...
2000 976
            $mapping['association'] = self::EMBED_MANY;
2001
        }
2002
2003 1387
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
2004 123
            $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
2005
        }
2006
2007
        /*
2008
        if (isset($mapping['type']) && ($mapping['type'] === 'one' || $mapping['type'] === 'many')) {
2009
            $mapping['type'] = $mapping['type'] === 'one' ? self::ONE : self::MANY;
2010
        }
2011
        */
2012 1387
        if (isset($mapping['version'])) {
2013 67
            $mapping['notSaved'] = true;
2014 67
            $this->setVersionMapping($mapping);
2015
        }
2016 1387
        if (isset($mapping['lock'])) {
2017 22
            $mapping['notSaved'] = true;
2018 22
            $this->setLockMapping($mapping);
2019
        }
2020 1387
        $mapping['isOwningSide'] = true;
2021 1387
        $mapping['isInverseSide'] = false;
2022 1387
        if (isset($mapping['reference'])) {
2023 1061 View Code Duplication
            if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
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...
2024 83
                $mapping['isOwningSide'] = true;
2025 83
                $mapping['isInverseSide'] = false;
2026
            }
2027 1061 View Code Duplication
            if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
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...
2028 812
                $mapping['isInverseSide'] = true;
2029 812
                $mapping['isOwningSide'] = false;
2030
            }
2031 1061 View Code Duplication
            if (isset($mapping['repositoryMethod'])) {
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...
2032 58
                $mapping['isInverseSide'] = true;
2033 58
                $mapping['isOwningSide'] = false;
2034
            }
2035 1061
            if (!isset($mapping['orphanRemoval'])) {
2036 1043
                $mapping['orphanRemoval'] = false;
2037
            }
2038
        }
2039
2040 1387
        if (!empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || !$mapping['isInverseSide'])) {
2041
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
2042
        }
2043
2044 1387
        $this->applyStorageStrategy($mapping);
2045
2046 1386
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
2047 1386
        if (isset($mapping['association'])) {
2048 1195
            $this->associationMappings[$mapping['fieldName']] = $mapping;
2049
        }
2050
2051 1386
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
2052 1385
        $reflProp->setAccessible(true);
2053 1385
        $this->reflFields[$mapping['fieldName']] = $reflProp;
2054
2055 1385
        return $mapping;
2056
    }
2057
2058
    /**
2059
     * Determines which fields get serialized.
2060
     *
2061
     * It is only serialized what is necessary for best unserialization performance.
2062
     * That means any metadata properties that are not set or empty or simply have
2063
     * their default value are NOT serialized.
2064
     *
2065
     * Parts that are also NOT serialized because they can not be properly unserialized:
2066
     *      - reflClass (ReflectionClass)
2067
     *      - reflFields (ReflectionProperty array)
2068
     *
2069
     * @return array The names of all the fields that should be serialized.
2070
     */
2071 6
    public function __sleep()
2072
    {
2073
        // This metadata is always serialized/cached.
2074
        $serialized = array(
2075 6
            'fieldMappings',
2076
            'associationMappings',
2077
            'identifier',
2078
            'name',
2079
            'namespace', // TODO: REMOVE
2080
            'db',
2081
            'collection',
2082
            'readPreference',
2083
            'readPreferenceTags',
2084
            'writeConcern',
2085
            'rootDocumentName',
2086
            'generatorType',
2087
            'generatorOptions',
2088
            'idGenerator',
2089
            'indexes',
2090
            'shardKey',
2091
        );
2092
2093
        // The rest of the metadata is only serialized if necessary.
2094 6
        if ($this->changeTrackingPolicy != self::CHANGETRACKING_DEFERRED_IMPLICIT) {
2095
            $serialized[] = 'changeTrackingPolicy';
2096
        }
2097
2098 6
        if ($this->customRepositoryClassName) {
2099 1
            $serialized[] = 'customRepositoryClassName';
2100
        }
2101
2102 6
        if ($this->inheritanceType != self::INHERITANCE_TYPE_NONE || $this->discriminatorField !== null) {
2103 4
            $serialized[] = 'inheritanceType';
2104 4
            $serialized[] = 'discriminatorField';
2105 4
            $serialized[] = 'discriminatorValue';
2106 4
            $serialized[] = 'discriminatorMap';
2107 4
            $serialized[] = 'defaultDiscriminatorValue';
2108 4
            $serialized[] = 'parentClasses';
2109 4
            $serialized[] = 'subClasses';
2110
        }
2111
2112 6
        if ($this->isMappedSuperclass) {
2113 1
            $serialized[] = 'isMappedSuperclass';
2114
        }
2115
2116 6
        if ($this->isEmbeddedDocument) {
2117 1
            $serialized[] = 'isEmbeddedDocument';
2118
        }
2119
2120 6
        if ($this->isQueryResultDocument) {
2121
            $serialized[] = 'isQueryResultDocument';
2122
        }
2123
2124 6
        if ($this->isVersioned) {
2125
            $serialized[] = 'isVersioned';
2126
            $serialized[] = 'versionField';
2127
        }
2128
2129 6
        if ($this->lifecycleCallbacks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->lifecycleCallbacks of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2130
            $serialized[] = 'lifecycleCallbacks';
2131
        }
2132
2133 6
        if ($this->collectionCapped) {
2134 1
            $serialized[] = 'collectionCapped';
2135 1
            $serialized[] = 'collectionSize';
2136 1
            $serialized[] = 'collectionMax';
2137
        }
2138
2139 6
        if ($this->isReadOnly) {
2140
            $serialized[] = 'isReadOnly';
2141
        }
2142
2143 6
        return $serialized;
2144
    }
2145
2146
    /**
2147
     * Restores some state that can not be serialized/unserialized.
2148
     *
2149
     * @return void
2150
     */
2151 6
    public function __wakeup()
2152
    {
2153
        // Restore ReflectionClass and properties
2154 6
        $this->reflClass = new \ReflectionClass($this->name);
2155 6
        $this->instantiator = $this->instantiator ?: new Instantiator();
2156
2157 6
        foreach ($this->fieldMappings as $field => $mapping) {
2158 3
            if (isset($mapping['declared'])) {
2159 1
                $reflField = new \ReflectionProperty($mapping['declared'], $field);
2160
            } else {
2161 3
                $reflField = $this->reflClass->getProperty($field);
2162
            }
2163 3
            $reflField->setAccessible(true);
2164 3
            $this->reflFields[$field] = $reflField;
2165
        }
2166 6
    }
2167
2168
    /**
2169
     * Creates a new instance of the mapped class, without invoking the constructor.
2170
     *
2171
     * @return object
2172
     */
2173 354
    public function newInstance()
2174
    {
2175 354
        return $this->instantiator->instantiate($this->name);
2176
    }
2177
}
2178