Completed
Pull Request — master (#1733)
by Gabriel
07:11 queued 02:16
created

ClassMetadata::setShardKey()   C

Complexity

Conditions 13
Paths 7

Size

Total Lines 42
Code Lines 25

Duplication

Lines 14
Ratio 33.33 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 0
Metric Value
dl 14
loc 42
ccs 0
cts 0
cp 0
rs 5.1234
c 0
b 0
f 0
cc 13
eloc 25
nc 7
nop 2
crap 182

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 1444
    /**
44
     * UUID means Doctrine will generate a uuid for us.
45 1444
     */
46 1444
    const GENERATOR_TYPE_UUID = 3;
47 1444
48 1444
    /**
49 1444
     * ALNUM means Doctrine will generate Alpha-numeric string identifiers, using the INCREMENT
50 1444
     * 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 1379
     * generator class.
59
     *
60 1379
     * The class  will have to be a subtype of AbstractIdGenerator.
61
     */
62 1378
    const GENERATOR_TYPE_CUSTOM = 5;
63 1377
64 1377
    /**
65 1377
     * 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 6
    const EMBED_ONE = 3;
81
    const EMBED_MANY = 4;
82
    const MANY = 'many';
83
    const ONE = 'one';
84 6
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 6
     */
104
    const INHERITANCE_TYPE_SINGLE_COLLECTION = 2;
105
106
    /**
107 6
     * COLLECTION_PER_CLASS means the class will be persisted according to the rules
108 1
     * of <tt>Concrete Collection Inheritance</tt>.
109
     */
110
    const INHERITANCE_TYPE_COLLECTION_PER_CLASS = 3;
111 6
112 4
    /**
113 4
     * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time
114 4
     * by doing a property-by-property comparison with the original data. This will
115 4
     * be done for all entities that are in MANAGED state at commit-time.
116 4
     *
117 4
     * This is the default change tracking policy.
118 4
     */
119
    const CHANGETRACKING_DEFERRED_IMPLICIT = 1;
120
121 6
    /**
122 1
     * 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 6
     */
126 1
    const CHANGETRACKING_DEFERRED_EXPLICIT = 2;
127
128
    /**
129 6
     * 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 6
    const CHANGETRACKING_NOTIFY = 3;
134
135
    /**
136
     * SET means that fields will be written to the database using a $set operator
137
     */
138 6
    const STORAGE_STRATEGY_SET = 'set';
139
140
    /**
141
     * INCREMENT means that fields will be written to the database by calculating
142 6
     * the difference and using the $inc operator
143 1
     */
144 1
    const STORAGE_STRATEGY_INCREMENT = 'increment';
145 1
146
    const STORAGE_STRATEGY_PUSH_ALL = 'pushAll';
147
    const STORAGE_STRATEGY_ADD_TO_SET = 'addToSet';
148 6
    const STORAGE_STRATEGY_ATOMIC_SET = 'atomicSet';
149
    const STORAGE_STRATEGY_ATOMIC_SET_ARRAY = 'atomicSetArray';
150
    const STORAGE_STRATEGY_SET_ARRAY = 'setArray';
151
152 6
    /**
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 6
    public $collection;
161
162
    /**
163 6
     * READ-ONLY: If the collection should be a fixed size.
164 6
     */
165
    public $collectionCapped;
166 6
167 3
    /**
168 1
     * READ-ONLY: If the collection is fixed size, its size in bytes.
169
     */
170 3
    public $collectionSize;
171
172 3
    /**
173 3
     * READ-ONLY: If the collection is fixed size, the maximum number of elements to store in the collection.
174
     */
175 6
    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 354
    /**
183
     * READ-ONLY Associated with readPreference Allows to specify criteria so that your application can target read
184 354
     * 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
    public function __construct($documentName)
448
    {
449
        $this->name = $documentName;
450
        $this->rootDocumentName = $documentName;
451
        $this->reflClass = new \ReflectionClass($documentName);
452
        $this->namespace = $this->reflClass->getNamespaceName();
453
        $this->setCollection($this->reflClass->getShortName());
454
        $this->instantiator = new Instantiator();
455
    }
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
    public static function getReferenceId($reference, $storeAs)
465
    {
466
        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
    private static function getReferencePrefix($storeAs)
475
    {
476
        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
        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
    public static function getReferenceFieldName($storeAs, $pathPrefix = '')
491
    {
492
        if ($storeAs === ClassMetadata::REFERENCE_STORE_AS_ID) {
493
            return $pathPrefix;
494
        }
495
496
        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
    public function getReflectionClass()
503
    {
504
        if ( ! $this->reflClass) {
505
            $this->reflClass = new \ReflectionClass($this->name);
506
        }
507
508
        return $this->reflClass;
509
    }
510
511
    /**
512
     * {@inheritDoc}
513
     */
514
    public function isIdentifier($fieldName)
515
    {
516
        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
    public function setIdentifier($identifier)
526
    {
527
        $this->identifier = $identifier;
528
    }
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
    public function getIdentifier()
537
    {
538
        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
    public function getIdentifierFieldNames()
548
    {
549
        return array($this->identifier);
550
    }
551
552
    /**
553
     * {@inheritDoc}
554
     */
555
    public function hasField($fieldName)
556
    {
557
        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
    public function setInheritanceType($type)
566
    {
567
        $this->inheritanceType = $type;
568
    }
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
    public function isInheritedField($fieldName)
578
    {
579
        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
    public function setCustomRepositoryClass($repositoryClassName)
588
    {
589
        if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
590
            return;
591
        }
592
593 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
            $repositoryClassName = $this->namespace . '\\' . $repositoryClassName;
595
        }
596
597
        $this->customRepositoryClassName = $repositoryClassName;
598
    }
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
    public function invokeLifecycleCallbacks($event, $document, array $arguments = null)
611
    {
612
        if ( ! $document instanceof $this->name) {
613
            throw new \InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"', $this->name, get_class($document)));
614
        }
615
616
        if (empty($this->lifecycleCallbacks[$event])) {
617
            return;
618
        }
619
620
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
621
            if ($arguments !== null) {
622
                call_user_func_array(array($document, $callback), $arguments);
623
            } else {
624
                $document->$callback();
625
            }
626
        }
627
    }
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
    public function addLifecycleCallback($callback, $event)
661
    {
662
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
663
            return;
664
        }
665
666
        $this->lifecycleCallbacks[$event][] = $callback;
667
    }
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
    public function setLifecycleCallbacks(array $callbacks)
677
    {
678
        $this->lifecycleCallbacks = $callbacks;
679
    }
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
    public function registerAlsoLoadMethod($method, $fields)
691
    {
692
        $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : array($fields);
693
    }
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
    public function setAlsoLoadMethods(array $methods)
703
    {
704
        $this->alsoLoadMethods = $methods;
705
    }
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
    public function setDiscriminatorField($discriminatorField)
720
    {
721
        if ($discriminatorField === null) {
722
            $this->discriminatorField = null;
723
724
            return;
725
        }
726
727
        // Handle array argument with name/fieldName keys for BC
728
        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
        foreach ($this->fieldMappings as $fieldMapping) {
737
            if ($discriminatorField == $fieldMapping['name']) {
738
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
739
            }
740
        }
741
742
        $this->discriminatorField = $discriminatorField;
743
    }
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
    public function setDiscriminatorMap(array $map)
754
    {
755
        foreach ($map as $value => $className) {
756
            if (strpos($className, '\\') === false && strlen($this->namespace)) {
757
                $className = $this->namespace . '\\' . $className;
758
            }
759
            $this->discriminatorMap[$value] = $className;
760
            if ($this->name == $className) {
761
                $this->discriminatorValue = $value;
762
            } else {
763
                if ( ! class_exists($className)) {
764
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
765
                }
766
                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
                    $this->subClasses[] = $className;
768
                }
769
            }
770
        }
771
    }
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
    public function setDefaultDiscriminatorValue($defaultDiscriminatorValue)
782
    {
783
        if ($defaultDiscriminatorValue === null) {
784
            $this->defaultDiscriminatorValue = null;
785
786
            return;
787
        }
788
789
        if (!array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
790
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
791
        }
792
793
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
794
    }
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
    public function setDiscriminatorValue($value)
804
    {
805
        $this->discriminatorMap[$value] = $this->name;
806
        $this->discriminatorValue = $value;
807
    }
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
    public function addIndex($keys, array $options = array())
816
    {
817
        $this->indexes[] = array(
818 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
                if ($value == 1 || $value == -1) {
820
                    return (int) $value;
821
                }
822
                if (is_string($value)) {
823
                    $lower = strtolower($value);
824
                    if ($lower === 'asc') {
825
                        return 1;
826
                    } elseif ($lower === 'desc') {
827
                        return -1;
828
                    }
829
                }
830
                return $value;
831
            }, $keys),
832
            'options' => $options
833
        );
834
    }
835
836
    /**
837
     * Returns the array of indexes for this Document.
838
     *
839
     * @return array $indexes The array of indexes.
840
     */
841
    public function getIndexes()
842
    {
843
        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
    public function setShardKey(array $keys, array $options = array())
865
    {
866
        if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && !is_null($this->shardKey)) {
867
            throw MappingException::shardKeyInSingleCollInheritanceSubclass($this->getName());
868
        }
869
870
        if ($this->isEmbeddedDocument) {
871
            throw MappingException::embeddedDocumentCantHaveShardKey($this->getName());
872
        }
873
874
        foreach (array_keys($keys) as $field) {
875
            if (! isset($this->fieldMappings[$field])) {
876
                continue;
877
            }
878
879
            if (in_array($this->fieldMappings[$field]['type'], ['many', 'collection'])) {
880
                throw MappingException::noMultiKeyShardKeys($this->getName(), $field);
881
            }
882
883
            if ($this->fieldMappings[$field]['strategy'] !== static::STORAGE_STRATEGY_SET) {
884
                throw MappingException::onlySetStrategyAllowedInShardKey($this->getName(), $field);
885
            }
886
        }
887
888
        $this->shardKey = array(
889 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
                if ($value == 1 || $value == -1) {
891
                    return (int) $value;
892
                }
893
                if (is_string($value)) {
894
                    $lower = strtolower($value);
895
                    if ($lower === 'asc') {
896
                        return 1;
897
                    } elseif ($lower === 'desc') {
898
                        return -1;
899
                    }
900
                }
901
                return $value;
902
            }, $keys),
903
            'options' => $options
904
        );
905
    }
906
907
    /**
908
     * @return array
909
     */
910
    public function getShardKey()
911
    {
912
        return $this->shardKey;
913
    }
914
915
    /**
916
     * Checks whether this document has shard key or not.
917
     *
918
     * @return bool
919
     */
920
    public function isSharded()
921
    {
922
        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
    public function setReadPreference($readPreference, $tags)
932
    {
933
        $this->readPreference = $readPreference;
934
        $this->readPreferenceTags = $tags;
935
    }
936
937
    /**
938
     * Sets the write concern used by this class.
939
     *
940
     * @param string $writeConcern
941
     */
942
    public function setWriteConcern($writeConcern)
943
    {
944
        $this->writeConcern = $writeConcern;
945
    }
946
947
    /**
948
     * @return string
949
     */
950
    public function getWriteConcern()
951
    {
952
        return $this->writeConcern;
953
    }
954
955
    /**
956
     * Whether there is a write concern configured for this class.
957
     *
958
     * @return bool
959
     */
960
    public function hasWriteConcern()
961
    {
962
        return $this->writeConcern !== null;
963
    }
964
965
    /**
966
     * Sets the change tracking policy used by this class.
967
     *
968
     * @param integer $policy
969
     */
970
    public function setChangeTrackingPolicy($policy)
971
    {
972
        $this->changeTrackingPolicy = $policy;
973
    }
974
975
    /**
976
     * Whether the change tracking policy of this class is "deferred explicit".
977
     *
978
     * @return boolean
979
     */
980
    public function isChangeTrackingDeferredExplicit()
981
    {
982
        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
    public function isChangeTrackingDeferredImplicit()
991
    {
992
        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
    public function isChangeTrackingNotify()
1001
    {
1002
        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
    public function getReflectionProperties()
1011
    {
1012
        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
    public function getName()
1031
    {
1032
        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
    public function getDatabase()
1051
    {
1052
        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
    public function setDatabase($db)
1061
    {
1062
        $this->db = $db;
1063
    }
1064
1065
    /**
1066
     * Get the collection this Document is mapped to.
1067
     *
1068
     * @return string $collection The collection name.
1069
     */
1070
    public function getCollection()
1071
    {
1072
        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
    public function setCollection($name)
1083
    {
1084
        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
            $this->collection = $name;
1094
        }
1095
    }
1096
1097
    /**
1098
     * Get whether or not the documents collection is capped.
1099
     *
1100
     * @return boolean
1101
     */
1102
    public function getCollectionCapped()
1103
    {
1104
        return $this->collectionCapped;
1105
    }
1106
1107
    /**
1108
     * Set whether or not the documents collection is capped.
1109
     *
1110
     * @param boolean $bool
1111
     */
1112
    public function setCollectionCapped($bool)
1113
    {
1114
        $this->collectionCapped = $bool;
1115
    }
1116
1117
    /**
1118
     * Get the collection size
1119
     *
1120
     * @return integer
1121
     */
1122
    public function getCollectionSize()
1123
    {
1124
        return $this->collectionSize;
1125
    }
1126
1127
    /**
1128
     * Set the collection size.
1129
     *
1130
     * @param integer $size
1131
     */
1132
    public function setCollectionSize($size)
1133
    {
1134
        $this->collectionSize = $size;
1135
    }
1136
1137
    /**
1138
     * Get the collection max.
1139
     *
1140
     * @return integer
1141
     */
1142
    public function getCollectionMax()
1143
    {
1144
        return $this->collectionMax;
1145
    }
1146
1147
    /**
1148
     * Set the collection max.
1149
     *
1150
     * @param integer $max
1151
     */
1152
    public function setCollectionMax($max)
1153
    {
1154
        $this->collectionMax = $max;
1155
    }
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
    private function applyStorageStrategy(array &$mapping)
1173
    {
1174
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1175
            return;
1176
        }
1177
1178
        switch (true) {
1179
            case $mapping['type'] == 'int':
1180
            case $mapping['type'] == 'float':
1181
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1182
                $allowedStrategies = [self::STORAGE_STRATEGY_SET, self::STORAGE_STRATEGY_INCREMENT];
1183
                break;
1184
1185
            case $mapping['type'] == 'many':
1186
                $defaultStrategy = CollectionHelper::DEFAULT_STRATEGY;
1187
                $allowedStrategies = [
1188
                    self::STORAGE_STRATEGY_PUSH_ALL,
1189
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1190
                    self::STORAGE_STRATEGY_SET,
1191
                    self::STORAGE_STRATEGY_SET_ARRAY,
1192
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1193
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1194
                ];
1195
                break;
1196
1197
            default:
1198
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1199
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1200
        }
1201
1202
        if (! isset($mapping['strategy'])) {
1203
            $mapping['strategy'] = $defaultStrategy;
1204
        }
1205
1206
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1207
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1208
        }
1209
1210
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1211
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1212
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1213
        }
1214
    }
1215
1216
    /**
1217
     * Map a single embedded document.
1218
     *
1219
     * @param array $mapping The mapping information.
1220
     */
1221
    public function mapOneEmbedded(array $mapping)
1222
    {
1223
        $mapping['embedded'] = true;
1224
        $mapping['type'] = 'one';
1225
        $this->mapField($mapping);
1226
    }
1227
1228
    /**
1229
     * Map a collection of embedded documents.
1230
     *
1231
     * @param array $mapping The mapping information.
1232
     */
1233
    public function mapManyEmbedded(array $mapping)
1234
    {
1235
        $mapping['embedded'] = true;
1236
        $mapping['type'] = 'many';
1237
        $this->mapField($mapping);
1238
    }
1239
1240
    /**
1241
     * Map a single document reference.
1242
     *
1243
     * @param array $mapping The mapping information.
1244
     */
1245
    public function mapOneReference(array $mapping)
1246
    {
1247
        $mapping['reference'] = true;
1248
        $mapping['type'] = 'one';
1249
        $this->mapField($mapping);
1250
    }
1251
1252
    /**
1253
     * Map a collection of document references.
1254
     *
1255
     * @param array $mapping The mapping information.
1256
     */
1257
    public function mapManyReference(array $mapping)
1258
    {
1259
        $mapping['reference'] = true;
1260
        $mapping['type'] = 'many';
1261
        $this->mapField($mapping);
1262
    }
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
    public function addInheritedFieldMapping(array $fieldMapping)
1272
    {
1273
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1274
1275
        if (isset($fieldMapping['association'])) {
1276
            $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1277
        }
1278
    }
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
    public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
1292
    {
1293
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1294
    }
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
    public function hasReference($fieldName)
1303
    {
1304
        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
    public function hasEmbed($fieldName)
1314
    {
1315
        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
    public function hasAssociation($fieldName)
1324
    {
1325
        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
    public function setIdGenerator($generator)
1408
    {
1409
        $this->idGenerator = $generator;
1410
    }
1411
1412
    /**
1413
     * Casts the identifier to its portable PHP type.
1414
     *
1415
     * @param mixed $id
1416
     * @return mixed $id
1417
     */
1418
    public function getPHPIdentifierValue($id)
1419
    {
1420
        $idType = $this->fieldMappings[$this->identifier]['type'];
1421
        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
    public function getDatabaseIdentifierValue($id)
1431
    {
1432
        $idType = $this->fieldMappings[$this->identifier]['type'];
1433
        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
    public function setIdentifierValue($document, $id)
1445
    {
1446
        $id = $this->getPHPIdentifierValue($id);
1447
        $this->reflFields[$this->identifier]->setValue($document, $id);
1448
    }
1449
1450
    /**
1451
     * Gets the document identifier as a PHP type.
1452
     *
1453
     * @param object $document
1454
     * @return mixed $id
1455
     */
1456
    public function getIdentifierValue($document)
1457
    {
1458
        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
    public function getIdentifierObject($document)
1481
    {
1482
        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
    public function setFieldValue($document, $field, $value)
1493
    {
1494
        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
            $document->__load();
1498
        }
1499
1500
        $this->reflFields[$field]->setValue($document, $value);
1501
    }
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
    public function getFieldValue($document, $field)
1512
    {
1513
        if ($document instanceof Proxy && $field !== $this->identifier && ! $document->__isInitialized()) {
1514
            $document->__load();
1515
        }
1516
1517
        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
    public function getFieldMapping($fieldName)
1530
    {
1531
        if ( ! isset($this->fieldMappings[$fieldName])) {
1532
            throw MappingException::mappingNotFound($this->name, $fieldName);
1533
        }
1534
        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
    public function getEmbeddedFieldsMappings()
1543
    {
1544
        return array_filter(
1545
            $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
    public function getFieldMappingByDbFieldName($dbFieldName)
1560
    {
1561
        foreach ($this->fieldMappings as $mapping) {
1562
            if ($mapping['name'] == $dbFieldName) {
1563
                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
    public function isNullable($fieldName)
1578
    {
1579
        $mapping = $this->getFieldMapping($fieldName);
1580
        if ($mapping !== false) {
1581
            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
    public function hasDiscriminator()
1592
    {
1593
        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
    public function setIdGeneratorType($generatorType)
1602
    {
1603
        $this->generatorType = $generatorType;
1604
    }
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
    public function isInheritanceTypeNone()
1620
    {
1621
        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
    public function isInheritanceTypeSingleCollection()
1630
    {
1631
        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
    public function setSubclasses(array $subclasses)
1650
    {
1651
        foreach ($subclasses as $subclass) {
1652
            if (strpos($subclass, '\\') === false && strlen($this->namespace)) {
1653
                $this->subClasses[] = $this->namespace . '\\' . $subclass;
1654
            } else {
1655
                $this->subClasses[] = $subclass;
1656
            }
1657
        }
1658
    }
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
    public function setParentClasses(array $classNames)
1668
    {
1669
        $this->parentClasses = $classNames;
1670
1671
        if (count($classNames) > 0) {
1672
            $this->rootDocumentName = array_pop($classNames);
1673
        }
1674
    }
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
    public function setVersionMapping(array &$mapping)
1725
    {
1726
        if ($mapping['type'] !== 'int' && $mapping['type'] !== 'date') {
1727
            throw LockException::invalidVersionFieldType($mapping['type']);
1728
        }
1729
1730
        $this->isVersioned  = true;
1731
        $this->versionField = $mapping['fieldName'];
1732
    }
1733
1734
    /**
1735
     * Sets whether this class is to be versioned for optimistic locking.
1736
     *
1737
     * @param boolean $bool
1738
     */
1739
    public function setVersioned($bool)
1740
    {
1741
        $this->isVersioned = $bool;
1742
    }
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
    public function setVersionField($versionField)
1751
    {
1752
        $this->versionField = $versionField;
1753
    }
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
    public function setLockMapping(array &$mapping)
1764
    {
1765
        if ($mapping['type'] !== 'int') {
1766
            throw LockException::invalidLockFieldType($mapping['type']);
1767
        }
1768
1769
        $this->isLockable = true;
1770
        $this->lockField = $mapping['fieldName'];
1771
    }
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
    public function markReadOnly()
1798
    {
1799
        $this->isReadOnly = true;
1800
    }
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
    public function getTypeOfField($fieldName)
1822
    {
1823
        return isset($this->fieldMappings[$fieldName]) ?
1824
            $this->fieldMappings[$fieldName]['type'] : null;
1825
    }
1826
1827
    /**
1828
     * {@inheritDoc}
1829
     */
1830
    public function getAssociationTargetClass($assocName)
1831
    {
1832
        if ( ! isset($this->associationMappings[$assocName])) {
1833
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1834
        }
1835
1836
        return $this->associationMappings[$assocName]['targetDocument'];
1837
    }
1838
1839
    /**
1840
     * Retrieve the collectionClass associated with an association
1841
     *
1842
     * @param string $assocName
1843
     */
1844
    public function getAssociationCollectionClass($assocName)
1845
    {
1846
        if ( ! isset($this->associationMappings[$assocName])) {
1847
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1848
        }
1849
1850
        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
        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
    public function mapField(array $mapping)
1883
    {
1884
        if ( ! isset($mapping['fieldName']) && isset($mapping['name'])) {
1885
            $mapping['fieldName'] = $mapping['name'];
1886
        }
1887
        if ( ! isset($mapping['fieldName'])) {
1888
            throw MappingException::missingFieldName($this->name);
1889
        }
1890
        if ( ! isset($mapping['name'])) {
1891
            $mapping['name'] = $mapping['fieldName'];
1892
        }
1893
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1894
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, $mapping['name']);
1895
        }
1896
        if (isset($this->fieldMappings[$mapping['fieldName']])) {
1897
            //throw MappingException::duplicateFieldMapping($this->name, $mapping['fieldName']);
1898
        }
1899
        if ($this->discriminatorField !== null && $this->discriminatorField == $mapping['name']) {
1900
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1901
        }
1902 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
            $mapping['targetDocument'] = $this->namespace . '\\' . $mapping['targetDocument'];
1904
        }
1905
        if (isset($mapping['collectionClass'])) {
1906 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
                $mapping['collectionClass'] = $this->namespace . '\\' . $mapping['collectionClass'];
1908
            }
1909
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1910
        }
1911
        if ( ! empty($mapping['collectionClass'])) {
1912
            $rColl = new \ReflectionClass($mapping['collectionClass']);
1913
            if ( ! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1914
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1915
            }
1916
        }
1917
1918
        if (isset($mapping['discriminatorMap'])) {
1919
            foreach ($mapping['discriminatorMap'] as $key => $class) {
1920 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
                    $mapping['discriminatorMap'][$key] = $this->namespace . '\\' . $class;
1922
                }
1923
            }
1924
        }
1925
1926
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1927
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1928
        }
1929
1930
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : array();
1931
1932
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
1933
            $cascades = array('remove', 'persist', 'refresh', 'merge', 'detach');
1934
        }
1935
1936
        if (isset($mapping['embedded'])) {
1937
            unset($mapping['cascade']);
1938
        } elseif (isset($mapping['cascade'])) {
1939
            $mapping['cascade'] = $cascades;
1940
        }
1941
1942
        $mapping['isCascadeRemove'] = in_array('remove', $cascades);
1943
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1944
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1945
        $mapping['isCascadeMerge'] = in_array('merge', $cascades);
1946
        $mapping['isCascadeDetach'] = in_array('detach', $cascades);
1947
1948
        if (isset($mapping['id']) && $mapping['id'] === true) {
1949
            $mapping['name'] = '_id';
1950
            $this->identifier = $mapping['fieldName'];
1951 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
                $this->generatorType = constant(ClassMetadata::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
1953
            }
1954
            $this->generatorOptions = $mapping['options'] ?? array();
1955
            switch ($this->generatorType) {
1956
                case self::GENERATOR_TYPE_AUTO:
1957
                    $mapping['type'] = 'id';
1958
                    break;
1959
                default:
1960
                    if ( ! empty($this->generatorOptions['type'])) {
1961
                        $mapping['type'] = $this->generatorOptions['type'];
1962
                    } elseif (empty($mapping['type'])) {
1963
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
1964
                    }
1965
            }
1966
            unset($this->generatorOptions['type']);
1967
        }
1968
1969
        if ( ! isset($mapping['nullable'])) {
1970
            $mapping['nullable'] = false;
1971
        }
1972
1973
        if (isset($mapping['reference'])
1974
            && isset($mapping['storeAs'])
1975
            && $mapping['storeAs'] === ClassMetadata::REFERENCE_STORE_AS_ID
1976
            && ! isset($mapping['targetDocument'])
1977
        ) {
1978
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
1979
        }
1980
1981
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
1982
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
1983
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
1984
        }
1985
1986
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
1987
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
1988
        }
1989
1990 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
            $mapping['association'] = self::REFERENCE_ONE;
1992
        }
1993 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
            $mapping['association'] = self::REFERENCE_MANY;
1995
        }
1996 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
            $mapping['association'] = self::EMBED_ONE;
1998
        }
1999 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
            $mapping['association'] = self::EMBED_MANY;
2001
        }
2002
2003
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
2004
            $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
        if (isset($mapping['version'])) {
2013
            $mapping['notSaved'] = true;
2014
            $this->setVersionMapping($mapping);
2015
        }
2016
        if (isset($mapping['lock'])) {
2017
            $mapping['notSaved'] = true;
2018
            $this->setLockMapping($mapping);
2019
        }
2020
        $mapping['isOwningSide'] = true;
2021
        $mapping['isInverseSide'] = false;
2022
        if (isset($mapping['reference'])) {
2023 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
                $mapping['isOwningSide'] = true;
2025
                $mapping['isInverseSide'] = false;
2026
            }
2027 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
                $mapping['isInverseSide'] = true;
2029
                $mapping['isOwningSide'] = false;
2030
            }
2031 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
                $mapping['isInverseSide'] = true;
2033
                $mapping['isOwningSide'] = false;
2034
            }
2035
            if (!isset($mapping['orphanRemoval'])) {
2036
                $mapping['orphanRemoval'] = false;
2037
            }
2038
        }
2039
2040
        if (!empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || !$mapping['isInverseSide'])) {
2041
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
2042
        }
2043
2044
        $this->applyStorageStrategy($mapping);
2045
2046
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
2047
        if (isset($mapping['association'])) {
2048
            $this->associationMappings[$mapping['fieldName']] = $mapping;
2049
        }
2050
2051
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
2052
        $reflProp->setAccessible(true);
2053
        $this->reflFields[$mapping['fieldName']] = $reflProp;
2054
2055
        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
    public function __sleep()
2072
    {
2073
        // This metadata is always serialized/cached.
2074
        $serialized = array(
2075
            '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
        if ($this->changeTrackingPolicy != self::CHANGETRACKING_DEFERRED_IMPLICIT) {
2095
            $serialized[] = 'changeTrackingPolicy';
2096
        }
2097
2098
        if ($this->customRepositoryClassName) {
2099
            $serialized[] = 'customRepositoryClassName';
2100
        }
2101
2102
        if ($this->inheritanceType != self::INHERITANCE_TYPE_NONE || $this->discriminatorField !== null) {
2103
            $serialized[] = 'inheritanceType';
2104
            $serialized[] = 'discriminatorField';
2105
            $serialized[] = 'discriminatorValue';
2106
            $serialized[] = 'discriminatorMap';
2107
            $serialized[] = 'defaultDiscriminatorValue';
2108
            $serialized[] = 'parentClasses';
2109
            $serialized[] = 'subClasses';
2110
        }
2111
2112
        if ($this->isMappedSuperclass) {
2113
            $serialized[] = 'isMappedSuperclass';
2114
        }
2115
2116
        if ($this->isEmbeddedDocument) {
2117
            $serialized[] = 'isEmbeddedDocument';
2118
        }
2119
2120
        if ($this->isQueryResultDocument) {
2121
            $serialized[] = 'isQueryResultDocument';
2122
        }
2123
2124
        if ($this->isVersioned) {
2125
            $serialized[] = 'isVersioned';
2126
            $serialized[] = 'versionField';
2127
        }
2128
2129
        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
        if ($this->collectionCapped) {
2134
            $serialized[] = 'collectionCapped';
2135
            $serialized[] = 'collectionSize';
2136
            $serialized[] = 'collectionMax';
2137
        }
2138
2139
        if ($this->isReadOnly) {
2140
            $serialized[] = 'isReadOnly';
2141
        }
2142
2143
        return $serialized;
2144
    }
2145
2146
    /**
2147
     * Restores some state that can not be serialized/unserialized.
2148
     *
2149
     * @return void
2150
     */
2151
    public function __wakeup()
2152
    {
2153
        // Restore ReflectionClass and properties
2154
        $this->reflClass = new \ReflectionClass($this->name);
2155
        $this->instantiator = $this->instantiator ?: new Instantiator();
2156
2157
        foreach ($this->fieldMappings as $field => $mapping) {
2158
            if (isset($mapping['declared'])) {
2159
                $reflField = new \ReflectionProperty($mapping['declared'], $field);
2160
            } else {
2161
                $reflField = $this->reflClass->getProperty($field);
2162
            }
2163
            $reflField->setAccessible(true);
2164
            $this->reflFields[$field] = $reflField;
2165
        }
2166
    }
2167
2168
    /**
2169
     * Creates a new instance of the mapped class, without invoking the constructor.
2170
     *
2171
     * @return object
2172
     */
2173
    public function newInstance()
2174
    {
2175
        return $this->instantiator->instantiate($this->name);
2176
    }
2177
}
2178