Completed
Pull Request — master (#1263)
by Andreas
14:36
created

ClassMetadataInfo::mapField()   F

Complexity

Conditions 83
Paths > 20000

Size

Total Lines 189
Code Lines 116

Duplication

Lines 43
Ratio 22.75 %

Code Coverage

Tests 152
CRAP Score 83.0018

Importance

Changes 11
Bugs 1 Features 3
Metric Value
c 11
b 1
f 3
dl 43
loc 189
ccs 152
cts 153
cp 0.9935
rs 2
cc 83
eloc 116
nc 4294967295
nop 1
crap 83.0018

How to fix   Long Method    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
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ODM\MongoDB\Mapping;
21
22
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
23
use Doctrine\ODM\MongoDB\LockException;
24
use Doctrine\ODM\MongoDB\Proxy\Proxy;
25
use Doctrine\ODM\MongoDB\Types\Type;
26
use InvalidArgumentException;
27
28
/**
29
 * A <tt>ClassMetadata</tt> instance holds all the object-document mapping metadata
30
 * of a document and it's references.
31
 *
32
 * Once populated, ClassMetadata instances are usually cached in a serialized form.
33
 *
34
 * <b>IMPORTANT NOTE:</b>
35
 *
36
 * The fields of this class are only public for 2 reasons:
37
 * 1) To allow fast READ access.
38
 * 2) To drastically reduce the size of a serialized instance (private/protected members
39
 *    get the whole class name, namespace inclusive, prepended to every property in
40
 *    the serialized representation).
41
 *
42
 * @since       1.0
43
 */
44
class ClassMetadataInfo implements \Doctrine\Common\Persistence\Mapping\ClassMetadata
45
{
46
    /* The Id generator types. */
47
    /**
48
     * AUTO means Doctrine will automatically create a new \MongoId instance for us.
49
     */
50
    const GENERATOR_TYPE_AUTO = 1;
51
52
    /**
53
     * INCREMENT means a separate collection is used for maintaining and incrementing id generation.
54
     * Offers full portability.
55
     */
56
    const GENERATOR_TYPE_INCREMENT = 2;
57
58
    /**
59
     * UUID means Doctrine will generate a uuid for us.
60
     */
61
    const GENERATOR_TYPE_UUID = 3;
62
63
    /**
64
     * ALNUM means Doctrine will generate Alpha-numeric string identifiers, using the INCREMENT
65
     * generator to ensure identifier uniqueness
66
     */
67
    const GENERATOR_TYPE_ALNUM = 4;
68
69
    /**
70
     * CUSTOM means Doctrine expect a class parameter. It will then try to initiate that class
71
     * and pass other options to the generator. It will throw an Exception if the class
72
     * does not exist or if an option was passed for that there is not setter in the new
73
     * generator class.
74
     *
75
     * The class  will have to be a subtype of AbstractIdGenerator.
76
     */
77
    const GENERATOR_TYPE_CUSTOM = 5;
78
79
    /**
80
     * NONE means Doctrine will not generate any id for us and you are responsible for manually
81
     * assigning an id.
82
     */
83
    const GENERATOR_TYPE_NONE = 6;
84
85
    /**
86
     * Default discriminator field name.
87
     *
88
     * This is used for associations value for associations where a that do not define a "targetDocument" or
89
     * "discriminatorField" option in their mapping.
90
     */
91
    const DEFAULT_DISCRIMINATOR_FIELD = '_doctrine_class_name';
92
93
    const REFERENCE_ONE = 1;
94
    const REFERENCE_MANY = 2;
95
    const EMBED_ONE = 3;
96
    const EMBED_MANY = 4;
97
    const MANY = 'many';
98
    const ONE = 'one';
99
100
    /**
101
     * The types of storeAs references
102
     */
103
    const REFERENCE_STORE_AS_ID = 'id';
104
    const REFERENCE_STORE_AS_DB_REF = 'dbRef';
105
    const REFERENCE_STORE_AS_DB_REF_WITH_DB = 'dbRefWithDb';
106
107
    /* The inheritance mapping types */
108
    /**
109
     * NONE means the class does not participate in an inheritance hierarchy
110
     * and therefore does not need an inheritance mapping type.
111
     */
112
    const INHERITANCE_TYPE_NONE = 1;
113
114
    /**
115
     * SINGLE_COLLECTION means the class will be persisted according to the rules of
116
     * <tt>Single Collection Inheritance</tt>.
117
     */
118
    const INHERITANCE_TYPE_SINGLE_COLLECTION = 2;
119
120
    /**
121
     * COLLECTION_PER_CLASS means the class will be persisted according to the rules
122
     * of <tt>Concrete Collection Inheritance</tt>.
123
     */
124
    const INHERITANCE_TYPE_COLLECTION_PER_CLASS = 3;
125
126
    /**
127
     * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time
128
     * by doing a property-by-property comparison with the original data. This will
129
     * be done for all entities that are in MANAGED state at commit-time.
130
     *
131
     * This is the default change tracking policy.
132
     */
133
    const CHANGETRACKING_DEFERRED_IMPLICIT = 1;
134
135
    /**
136
     * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time
137
     * by doing a property-by-property comparison with the original data. This will
138
     * be done only for entities that were explicitly saved (through persist() or a cascade).
139
     */
140
    const CHANGETRACKING_DEFERRED_EXPLICIT = 2;
141
142
    /**
143
     * NOTIFY means that Doctrine relies on the entities sending out notifications
144
     * when their properties change. Such entity classes must implement
145
     * the <tt>NotifyPropertyChanged</tt> interface.
146
     */
147
    const CHANGETRACKING_NOTIFY = 3;
148
149
    /**
150
     * SET means that fields will be written to the database using a $set operator
151
     */
152
    const STORAGE_STRATEGY_SET = 'set';
153
154
    /**
155
     * INCREMENT means that fields will be written to the database by calculating
156
     * the difference and using the $inc operator
157
     */
158
    const STORAGE_STRATEGY_INCREMENT = 'increment';
159
160
    const STORAGE_STRATEGY_PUSH_ALL = 'pushAll';
161
    const STORAGE_STRATEGY_ADD_TO_SET = 'addToSet';
162
    const STORAGE_STRATEGY_ATOMIC_SET = 'atomicSet';
163
    const STORAGE_STRATEGY_ATOMIC_SET_ARRAY = 'atomicSetArray';
164
    const STORAGE_STRATEGY_SET_ARRAY = 'setArray';
165
166
    /**
167
     * READ-ONLY: The name of the mongo database the document is mapped to.
168
     */
169
    public $db;
170
171
    /**
172
     * READ-ONLY: The name of the mongo collection the document is mapped to.
173
     */
174
    public $collection;
175
176
    /**
177
     * READ-ONLY: If the collection should be a fixed size.
178
     */
179
    public $collectionCapped;
180
181
    /**
182
     * READ-ONLY: If the collection is fixed size, its size in bytes.
183
     */
184
    public $collectionSize;
185
186
    /**
187
     * READ-ONLY: If the collection is fixed size, the maximum number of elements to store in the collection.
188
     */
189
    public $collectionMax;
190
191
    /**
192
     * READ-ONLY: The field name of the document identifier.
193
     */
194
    public $identifier;
195
196
    /**
197
     * READ-ONLY: The field that stores a file reference and indicates the
198
     * document is a file and should be stored on the MongoGridFS.
199
     */
200
    public $file;
201
202
    /**
203
     * READ-ONLY: The field that stores the calculated distance when performing geo spatial
204
     * queries.
205
     */
206
    public $distance;
207
208
    /**
209
     * READ-ONLY: Whether or not reads for this class are okay to read from a slave.
210
     */
211
    public $slaveOkay;
212
213
    /**
214
     * READ-ONLY: The array of indexes for the document collection.
215
     */
216
    public $indexes = array();
217
218
    /**
219
     * READ-ONLY: Whether or not queries on this document should require indexes.
220
     */
221
    public $requireIndexes = false;
222
223
    /**
224
     * READ-ONLY: The name of the document class.
225
     */
226
    public $name;
227
228
    /**
229
     * READ-ONLY: The namespace the document class is contained in.
230
     *
231
     * @var string
232
     * @todo Not really needed. Usage could be localized.
233
     */
234
    public $namespace;
235
236
    /**
237
     * READ-ONLY: The name of the document class that is at the root of the mapped document inheritance
238
     * hierarchy. If the document is not part of a mapped inheritance hierarchy this is the same
239
     * as {@link $documentName}.
240
     *
241
     * @var string
242
     */
243
    public $rootDocumentName;
244
245
    /**
246
     * The name of the custom repository class used for the document class.
247
     * (Optional).
248
     *
249
     * @var string
250
     */
251
    public $customRepositoryClassName;
252
253
    /**
254
     * READ-ONLY: The names of the parent classes (ancestors).
255
     *
256
     * @var array
257
     */
258
    public $parentClasses = array();
259
260
    /**
261
     * READ-ONLY: The names of all subclasses (descendants).
262
     *
263
     * @var array
264
     */
265
    public $subClasses = array();
266
267
    /**
268
     * The ReflectionProperty instances of the mapped class.
269
     *
270
     * @var \ReflectionProperty[]
271
     */
272
    public $reflFields = array();
273
274
    /**
275
     * READ-ONLY: The inheritance mapping type used by the class.
276
     *
277
     * @var integer
278
     */
279
    public $inheritanceType = self::INHERITANCE_TYPE_NONE;
280
281
    /**
282
     * READ-ONLY: The Id generator type used by the class.
283
     *
284
     * @var string
285
     */
286
    public $generatorType = self::GENERATOR_TYPE_AUTO;
287
288
    /**
289
     * READ-ONLY: The Id generator options.
290
     *
291
     * @var array
292
     */
293
    public $generatorOptions = array();
294
295
    /**
296
     * READ-ONLY: The ID generator used for generating IDs for this class.
297
     *
298
     * @var \Doctrine\ODM\MongoDB\Id\AbstractIdGenerator
299
     */
300
    public $idGenerator;
301
302
    /**
303
     * READ-ONLY: The field mappings of the class.
304
     * Keys are field names and values are mapping definitions.
305
     *
306
     * The mapping definition array has the following values:
307
     *
308
     * - <b>fieldName</b> (string)
309
     * The name of the field in the Document.
310
     *
311
     * - <b>id</b> (boolean, optional)
312
     * Marks the field as the primary key of the document. Multiple fields of an
313
     * document can have the id attribute, forming a composite key.
314
     *
315
     * @var array
316
     */
317
    public $fieldMappings = array();
318
319
    /**
320
     * READ-ONLY: The association mappings of the class.
321
     * Keys are field names and values are mapping definitions.
322
     *
323
     * @var array
324
     */
325
    public $associationMappings = array();
326
327
    /**
328
     * READ-ONLY: Array of fields to also load with a given method.
329
     *
330
     * @var array
331
     */
332
    public $alsoLoadMethods = array();
333
334
    /**
335
     * READ-ONLY: The registered lifecycle callbacks for documents of this class.
336
     *
337
     * @var array
338
     */
339
    public $lifecycleCallbacks = array();
340
341
    /**
342
     * READ-ONLY: The discriminator value of this class.
343
     *
344
     * <b>This does only apply to the JOINED and SINGLE_COLLECTION inheritance mapping strategies
345
     * where a discriminator field is used.</b>
346
     *
347
     * @var mixed
348
     * @see discriminatorField
349
     */
350
    public $discriminatorValue;
351
352
    /**
353
     * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
354
     *
355
     * <b>This does only apply to the SINGLE_COLLECTION inheritance mapping strategy
356
     * where a discriminator field is used.</b>
357
     *
358
     * @var mixed
359
     * @see discriminatorField
360
     */
361
    public $discriminatorMap = array();
362
363
    /**
364
     * READ-ONLY: The definition of the discriminator field used in SINGLE_COLLECTION
365
     * inheritance mapping.
366
     *
367
     * @var string
368
     */
369
    public $discriminatorField;
370
371
    /**
372
     * READ-ONLY: The default value for discriminatorField in case it's not set in the document
373
     *
374
     * @var string
375
     * @see discriminatorField
376
     */
377
    public $defaultDiscriminatorValue;
378
379
    /**
380
     * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
381
     *
382
     * @var boolean
383
     */
384
    public $isMappedSuperclass = false;
385
386
    /**
387
     * READ-ONLY: Whether this class describes the mapping of a embedded document.
388
     *
389
     * @var boolean
390
     */
391
    public $isEmbeddedDocument = false;
392
393
    /**
394
     * READ-ONLY: Whether this class describes the mapping of an aggregation result document.
395
     *
396
     * @var boolean
397
     */
398
    public $isAggregationResultDocument = false;
399
400
    /**
401
     * READ-ONLY: The policy used for change-tracking on entities of this class.
402
     *
403
     * @var integer
404
     */
405
    public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;
406
407
    /**
408
     * READ-ONLY: A flag for whether or not instances of this class are to be versioned
409
     * with optimistic locking.
410
     *
411
     * @var boolean $isVersioned
412
     */
413
    public $isVersioned;
414
415
    /**
416
     * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
417
     *
418
     * @var mixed $versionField
419
     */
420
    public $versionField;
421
422
    /**
423
     * READ-ONLY: A flag for whether or not instances of this class are to allow pessimistic
424
     * locking.
425
     *
426
     * @var boolean $isLockable
427
     */
428
    public $isLockable;
429
430
    /**
431
     * READ-ONLY: The name of the field which is used for locking a document.
432
     *
433
     * @var mixed $lockField
434
     */
435
    public $lockField;
436
437
    /**
438
     * The ReflectionClass instance of the mapped class.
439
     *
440
     * @var \ReflectionClass
441
     */
442
    public $reflClass;
443
444
    /**
445
     * Initializes a new ClassMetadata instance that will hold the object-document mapping
446
     * metadata of the class with the given name.
447
     *
448
     * @param string $documentName The name of the document class the new instance is used for.
449
     */
450 883
    public function __construct($documentName)
451
    {
452 883
        $this->name = $documentName;
453 883
        $this->rootDocumentName = $documentName;
454 883
    }
455
456
    /**
457
     * {@inheritDoc}
458
     */
459 830
    public function getReflectionClass()
460
    {
461 830
        if ( ! $this->reflClass) {
462 2
            $this->reflClass = new \ReflectionClass($this->name);
463 2
        }
464
465 830
        return $this->reflClass;
466
    }
467
468
    /**
469
     * {@inheritDoc}
470
     */
471 3
    public function isIdentifier($fieldName)
472
    {
473 3
        return $this->identifier === $fieldName;
474
    }
475
476
    /**
477
     * INTERNAL:
478
     * Sets the mapped identifier field of this class.
479
     *
480
     * @param string $identifier
481
     */
482 332
    public function setIdentifier($identifier)
483
    {
484 332
        $this->identifier = $identifier;
485 332
    }
486
487
    /**
488
     * {@inheritDoc}
489
     *
490
     * Since MongoDB only allows exactly one identifier field
491
     * this will always return an array with only one value
492
     */
493
    public function getIdentifier()
494
    {
495
        return array($this->identifier);
496
    }
497
498
    /**
499
     * {@inheritDoc}
500
     *
501
     * Since MongoDB only allows exactly one identifier field
502
     * this will always return an array with only one value
503
     */
504
    public function getIdentifierFieldNames()
505
    {
506
        return array($this->identifier);
507
    }
508
509
    /**
510
     * {@inheritDoc}
511
     */
512 9
    public function hasField($fieldName)
513
    {
514 9
        return isset($this->fieldMappings[$fieldName]);
515
    }
516
517
    /**
518
     * Sets the inheritance type used by the class and it's subclasses.
519
     *
520
     * @param integer $type
521
     */
522 341
    public function setInheritanceType($type)
523
    {
524 341
        $this->inheritanceType = $type;
525 341
    }
526
527
    /**
528
     * Checks whether a mapped field is inherited from an entity superclass.
529
     *
530
     * @param  string $fieldName
531
     *
532
     * @return boolean TRUE if the field is inherited, FALSE otherwise.
533
     */
534 830
    public function isInheritedField($fieldName)
535
    {
536 830
        return isset($this->fieldMappings[$fieldName]['inherited']);
537
    }
538
539
    /**
540
     * Registers a custom repository class for the document class.
541
     *
542
     * @param string $repositoryClassName The class name of the custom repository.
543
     */
544 293
    public function setCustomRepositoryClass($repositoryClassName)
545
    {
546 293
        if ($this->isEmbeddedDocument || $this->isAggregationResultDocument) {
547
            return;
548
        }
549
550 293
        if ($repositoryClassName && strpos($repositoryClassName, '\\') === false && strlen($this->namespace)) {
551 3
            $repositoryClassName = $this->namespace . '\\' . $repositoryClassName;
552 3
        }
553
554 293
        $this->customRepositoryClassName = $repositoryClassName;
555 293
    }
556
557
    /**
558
     * Dispatches the lifecycle event of the given document by invoking all
559
     * registered callbacks.
560
     *
561
     * @param string $event     Lifecycle event
562
     * @param object $document  Document on which the event occurred
563
     * @param array  $arguments Arguments to pass to all callbacks
564
     * @throws \InvalidArgumentException if document class is not this class or
565
     *                                   a Proxy of this class
566
     */
567 573
    public function invokeLifecycleCallbacks($event, $document, array $arguments = null)
568
    {
569 573
        if ( ! $document instanceof $this->name) {
570 1
            throw new \InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"', $this->name, get_class($document)));
571
        }
572
573 572
        if (empty($this->lifecycleCallbacks[$event])) {
574 493
            return;
575
        }
576
577 167
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
578 167
            if ($arguments !== null) {
579 166
                call_user_func_array(array($document, $callback), $arguments);
580 166
            } else {
581 1
                $document->$callback();
582
            }
583 167
        }
584 167
    }
585
586
    /**
587
     * Checks whether the class has callbacks registered for a lifecycle event.
588
     *
589
     * @param string $event Lifecycle event
590
     *
591
     * @return boolean
592
     */
593
    public function hasLifecycleCallbacks($event)
594
    {
595
        return ! empty($this->lifecycleCallbacks[$event]);
596
    }
597
598
    /**
599
     * Gets the registered lifecycle callbacks for an event.
600
     *
601
     * @param string $event
602
     * @return array
603
     */
604
    public function getLifecycleCallbacks($event)
605
    {
606
        return isset($this->lifecycleCallbacks[$event]) ? $this->lifecycleCallbacks[$event] : array();
607
    }
608
609
    /**
610
     * Adds a lifecycle callback for documents of this class.
611
     *
612
     * If the callback is already registered, this is a NOOP.
613
     *
614
     * @param string $callback
615
     * @param string $event
616
     */
617 275
    public function addLifecycleCallback($callback, $event)
618
    {
619 275
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
620 1
            return;
621
        }
622
623 275
        $this->lifecycleCallbacks[$event][] = $callback;
624 275
    }
625
626
    /**
627
     * Sets the lifecycle callbacks for documents of this class.
628
     *
629
     * Any previously registered callbacks are overwritten.
630
     *
631
     * @param array $callbacks
632
     */
633 331
    public function setLifecycleCallbacks(array $callbacks)
634
    {
635 331
        $this->lifecycleCallbacks = $callbacks;
636 331
    }
637
638
    /**
639
     * Registers a method for loading document data before field hydration.
640
     *
641
     * Note: A method may be registered multiple times for different fields.
642
     * it will be invoked only once for the first field found.
643
     *
644
     * @param string       $method Method name
645
     * @param array|string $fields Database field name(s)
646
     */
647 15
    public function registerAlsoLoadMethod($method, $fields)
648
    {
649 15
        $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : array($fields);
650 15
    }
651
652
    /**
653
     * Sets the AlsoLoad methods for documents of this class.
654
     *
655
     * Any previously registered methods are overwritten.
656
     *
657
     * @param array $methods
658
     */
659 331
    public function setAlsoLoadMethods(array $methods)
660
    {
661 331
        $this->alsoLoadMethods = $methods;
662 331
    }
663
664
    /**
665
     * Sets the discriminator field.
666
     *
667
     * The field name is the the unmapped database field. Discriminator values
668
     * are only used to discern the hydration class and are not mapped to class
669
     * properties.
670
     *
671
     * @param string $discriminatorField
672
     *
673
     * @throws MappingException If the discriminator field conflicts with the
674
     *                          "name" attribute of a mapped field.
675
     */
676 350
    public function setDiscriminatorField($discriminatorField)
677
    {
678 350
        if ($discriminatorField === null) {
679 296
            $this->discriminatorField = null;
680
681 296
            return;
682
        }
683
684
        // Handle array argument with name/fieldName keys for BC
685 106
        if (is_array($discriminatorField)) {
686
            if (isset($discriminatorField['name'])) {
687
                $discriminatorField = $discriminatorField['name'];
688
            } elseif (isset($discriminatorField['fieldName'])) {
689
                $discriminatorField = $discriminatorField['fieldName'];
690
            }
691
        }
692
693 106
        foreach ($this->fieldMappings as $fieldMapping) {
694 3
            if ($discriminatorField == $fieldMapping['name']) {
695 1
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
696
            }
697 105
        }
698
699 105
        $this->discriminatorField = $discriminatorField;
700 105
    }
701
702
    /**
703
     * Sets the discriminator values used by this class.
704
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
705
     *
706
     * @param array $map
707
     *
708
     * @throws MappingException
709
     */
710 346
    public function setDiscriminatorMap(array $map)
711
    {
712 346
        foreach ($map as $value => $className) {
713 104
            if (strpos($className, '\\') === false && strlen($this->namespace)) {
714 80
                $className = $this->namespace . '\\' . $className;
715 80
            }
716 104
            $this->discriminatorMap[$value] = $className;
717 104
            if ($this->name == $className) {
718 98
                $this->discriminatorValue = $value;
719 98
            } else {
720 96
                if ( ! class_exists($className)) {
721
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
722
                }
723 96
                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...
724 82
                    $this->subClasses[] = $className;
725 82
                }
726
            }
727 346
        }
728 346
    }
729
730
    /**
731
     * Sets the default discriminator value to be used for this class
732
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies if the document has no discriminator value
733
     *
734
     * @param string $defaultDiscriminatorValue
735
     *
736
     * @throws MappingException
737
     */
738 337
    public function setDefaultDiscriminatorValue($defaultDiscriminatorValue)
739
    {
740 337
        if ($defaultDiscriminatorValue === null) {
741 331
            $this->defaultDiscriminatorValue = null;
742
743 331
            return;
744
        }
745
746 52
        if (!array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
747
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
748
        }
749
750 52
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
751 52
    }
752
753
    /**
754
     * Sets the discriminator value for this class.
755
     * Used for JOINED/SINGLE_TABLE inheritance and multiple document types in a single
756
     * collection.
757
     *
758
     * @param string $value
759
     */
760
    public function setDiscriminatorValue($value)
761
    {
762
        $this->discriminatorMap[$value] = $this->name;
763
        $this->discriminatorValue = $value;
764
    }
765
766
    /**
767
     * Sets the slaveOkay option applied to collections for this class.
768
     *
769
     * @param boolean|null $slaveOkay
770
     */
771 2
    public function setSlaveOkay($slaveOkay)
772
    {
773 2
        $this->slaveOkay = $slaveOkay === null ? null : (boolean) $slaveOkay;
774 2
    }
775
776
    /**
777
     * Add a index for this Document.
778
     *
779
     * @param array $keys Array of keys for the index.
780
     * @param array $options Array of options for the index.
781
     */
782 129
    public function addIndex($keys, array $options = array())
783
    {
784 129
        $this->indexes[] = array(
785
            'keys' => array_map(function($value) {
786 129
                if ($value == 1 || $value == -1) {
787 53
                    return (int) $value;
788
                }
789 121
                if (is_string($value)) {
790 121
                    $lower = strtolower($value);
791 121
                    if ($lower === 'asc') {
792 114
                        return 1;
793 10
                    } elseif ($lower === 'desc') {
794 3
                        return -1;
795
                    }
796 7
                }
797 7
                return $value;
798 129
            }, $keys),
799
            'options' => $options
800 129
        );
801 129
    }
802
803
    /**
804
     * Set whether or not queries on this document should require indexes.
805
     *
806
     * @param bool $requireIndexes
807
     */
808 821
    public function setRequireIndexes($requireIndexes)
809
    {
810 821
        $this->requireIndexes = $requireIndexes;
811 821
    }
812
813
    /**
814
     * Returns the array of indexes for this Document.
815
     *
816
     * @return array $indexes The array of indexes.
817
     */
818 12
    public function getIndexes()
819
    {
820 12
        return $this->indexes;
821
    }
822
823
    /**
824
     * Checks whether this document has indexes or not.
825
     *
826
     * @return boolean
827
     */
828
    public function hasIndexes()
829
    {
830
        return $this->indexes ? true : false;
831
    }
832
833
    /**
834
     * Sets the change tracking policy used by this class.
835
     *
836
     * @param integer $policy
837
     */
838 336
    public function setChangeTrackingPolicy($policy)
839
    {
840 336
        $this->changeTrackingPolicy = $policy;
841 336
    }
842
843
    /**
844
     * Whether the change tracking policy of this class is "deferred explicit".
845
     *
846
     * @return boolean
847
     */
848 35
    public function isChangeTrackingDeferredExplicit()
849
    {
850 35
        return $this->changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_EXPLICIT;
851
    }
852
853
    /**
854
     * Whether the change tracking policy of this class is "deferred implicit".
855
     *
856
     * @return boolean
857
     */
858 543
    public function isChangeTrackingDeferredImplicit()
859
    {
860 543
        return $this->changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_IMPLICIT;
861
    }
862
863
    /**
864
     * Whether the change tracking policy of this class is "notify".
865
     *
866
     * @return boolean
867
     */
868 11
    public function isChangeTrackingNotify()
869
    {
870 11
        return $this->changeTrackingPolicy == self::CHANGETRACKING_NOTIFY;
871
    }
872
873
    /**
874
     * Gets the ReflectionProperties of the mapped class.
875
     *
876
     * @return array An array of ReflectionProperty instances.
877
     */
878 1
    public function getReflectionProperties()
879
    {
880 1
        return $this->reflFields;
881
    }
882
883
    /**
884
     * Gets a ReflectionProperty for a specific field of the mapped class.
885
     *
886
     * @param string $name
887
     *
888
     * @return \ReflectionProperty
889
     */
890
    public function getReflectionProperty($name)
891
    {
892
        return $this->reflFields[$name];
893
    }
894
895
    /**
896
     * {@inheritDoc}
897
     */
898 827
    public function getName()
899
    {
900 827
        return $this->name;
901
    }
902
903
    /**
904
     * The namespace this Document class belongs to.
905
     *
906
     * @return string $namespace The namespace name.
907
     */
908
    public function getNamespace()
909
    {
910
        return $this->namespace;
911
    }
912
913
    /**
914
     * Returns the database this Document is mapped to.
915
     *
916
     * @return string $db The database name.
917
     */
918 766
    public function getDatabase()
919
    {
920 766
        return $this->db;
921
    }
922
923
    /**
924
     * Set the database this Document is mapped to.
925
     *
926
     * @param string $db The database name
927
     */
928 86
    public function setDatabase($db)
929
    {
930 86
        $this->db = $db;
931 86
    }
932
933
    /**
934
     * Get the collection this Document is mapped to.
935
     *
936
     * @return string $collection The collection name.
937
     */
938 758
    public function getCollection()
939
    {
940 758
        return $this->collection;
941
    }
942
943
    /**
944
     * Sets the collection this Document is mapped to.
945
     *
946
     * @param array|string $name
947
     *
948
     * @throws \InvalidArgumentException
949
     */
950 860
    public function setCollection($name)
951
    {
952 860
        if (is_array($name)) {
953
            if ( ! isset($name['name'])) {
954
                throw new \InvalidArgumentException('A name key is required when passing an array to setCollection()');
955
            }
956
            $this->collectionCapped = isset($name['capped']) ? $name['capped'] : false;
957
            $this->collectionSize = isset($name['size']) ? $name['size'] : 0;
958
            $this->collectionMax = isset($name['max']) ? $name['max'] : 0;
959
            $this->collection = $name['name'];
960
        } else {
961 860
            $this->collection = $name;
962
        }
963 860
    }
964
965
    /**
966
     * Get whether or not the documents collection is capped.
967
     *
968
     * @return boolean
969
     */
970 3
    public function getCollectionCapped()
971
    {
972 3
        return $this->collectionCapped;
973
    }
974
975
    /**
976
     * Set whether or not the documents collection is capped.
977
     *
978
     * @param boolean $bool
979
     */
980 1
    public function setCollectionCapped($bool)
981
    {
982 1
        $this->collectionCapped = $bool;
983 1
    }
984
985
    /**
986
     * Get the collection size
987
     *
988
     * @return integer
989
     */
990 3
    public function getCollectionSize()
991
    {
992 3
        return $this->collectionSize;
993
    }
994
995
    /**
996
     * Set the collection size.
997
     *
998
     * @param integer $size
999
     */
1000 1
    public function setCollectionSize($size)
1001
    {
1002 1
        $this->collectionSize = $size;
1003 1
    }
1004
1005
    /**
1006
     * Get the collection max.
1007
     *
1008
     * @return integer
1009
     */
1010 3
    public function getCollectionMax()
1011
    {
1012 3
        return $this->collectionMax;
1013
    }
1014
1015
    /**
1016
     * Set the collection max.
1017
     *
1018
     * @param integer $max
1019
     */
1020 1
    public function setCollectionMax($max)
1021
    {
1022 1
        $this->collectionMax = $max;
1023 1
    }
1024
1025
    /**
1026
     * Returns TRUE if this Document is mapped to a collection FALSE otherwise.
1027
     *
1028
     * @return boolean
1029
     */
1030
    public function isMappedToCollection()
1031
    {
1032
        return $this->collection ? true : false;
1033
    }
1034
1035
    /**
1036
     * Returns TRUE if this Document is a file to be stored on the MongoGridFS FALSE otherwise.
1037
     *
1038
     * @return boolean
1039
     */
1040 3
    public function isFile()
1041
    {
1042 3
        return $this->file ? true : false;
1043
    }
1044
1045
    /**
1046
     * Returns the file field name.
1047
     *
1048
     * @return string $file The file field name.
1049
     */
1050 331
    public function getFile()
1051
    {
1052 331
        return $this->file;
1053
    }
1054
1055
    /**
1056
     * Set the field name that stores the grid file.
1057
     *
1058
     * @param string $file
1059
     */
1060 332
    public function setFile($file)
1061
    {
1062 332
        $this->file = $file;
1063 332
    }
1064
1065
    /**
1066
     * Returns the distance field name.
1067
     *
1068
     * @return string $distance The distance field name.
1069
     */
1070
    public function getDistance()
1071
    {
1072
        return $this->distance;
1073
    }
1074
1075
    /**
1076
     * Set the field name that stores the distance.
1077
     *
1078
     * @param string $distance
1079
     */
1080 1
    public function setDistance($distance)
1081
    {
1082 1
        $this->distance = $distance;
1083 1
    }
1084
1085
    /**
1086
     * Map a field.
1087
     *
1088
     * @param array $mapping The mapping information.
1089
     *
1090
     * @return array
1091
     *
1092
     * @throws MappingException
1093
     */
1094 869
    public function mapField(array $mapping)
1095
    {
1096 869
        if ( ! isset($mapping['fieldName']) && isset($mapping['name'])) {
1097 8
            $mapping['fieldName'] = $mapping['name'];
1098 8
        }
1099 869
        if ( ! isset($mapping['fieldName'])) {
1100
            throw MappingException::missingFieldName($this->name);
1101
        }
1102 869
        if ( ! isset($mapping['name'])) {
1103 860
            $mapping['name'] = $mapping['fieldName'];
1104 860
        }
1105 869
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1106 1
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, $mapping['name']);
1107
        }
1108 868
        if (isset($this->fieldMappings[$mapping['fieldName']])) {
1109
            //throw MappingException::duplicateFieldMapping($this->name, $mapping['fieldName']);
1110 53
        }
1111 868
        if ($this->discriminatorField !== null && $this->discriminatorField == $mapping['name']) {
1112 1
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1113
        }
1114 867 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...
1115 559
            $mapping['targetDocument'] = $this->namespace . '\\' . $mapping['targetDocument'];
1116 559
        }
1117 867
        if (isset($mapping['collectionClass'])) {
1118 54 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...
1119 53
                $mapping['collectionClass'] = $this->namespace . '\\' . $mapping['collectionClass'];
1120 53
            }
1121 54
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1122 54
        }
1123 867
        if ( ! empty($mapping['collectionClass'])) {
1124 54
            $rColl = new \ReflectionClass($mapping['collectionClass']);
1125 54
            if ( ! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1126 1
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1127
            }
1128 53
        }
1129
1130 866
        if (isset($mapping['discriminatorMap'])) {
1131 99
            foreach ($mapping['discriminatorMap'] as $key => $class) {
1132 99 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...
1133 66
                    $mapping['discriminatorMap'][$key] = $this->namespace . '\\' . $class;
1134 66
                }
1135 99
            }
1136 99
        }
1137
1138 866
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1139 1
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1140
        }
1141
1142 865
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : array();
1143
1144 865
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
1145 588
            $cascades = array('remove', 'persist', 'refresh', 'merge', 'detach');
1146 588
        }
1147
1148 865
        if (isset($mapping['embedded'])) {
1149 551
            unset($mapping['cascade']);
1150 865
        } elseif (isset($mapping['cascade'])) {
1151 377
            $mapping['cascade'] = $cascades;
1152 377
        }
1153
1154 865
        $mapping['isCascadeRemove'] = in_array('remove', $cascades);
1155 865
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1156 865
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1157 865
        $mapping['isCascadeMerge'] = in_array('merge', $cascades);
1158 865
        $mapping['isCascadeDetach'] = in_array('detach', $cascades);
1159
1160 865
        if (isset($mapping['type']) && $mapping['type'] === 'file') {
1161 58
            $mapping['file'] = true;
1162 58
        }
1163 865
        if (isset($mapping['type']) && $mapping['type'] === 'increment') {
1164 1
            $mapping['strategy'] = self::STORAGE_STRATEGY_INCREMENT;
1165 1
        }
1166 865 View Code Duplication
        if (isset($mapping['file']) && $mapping['file'] === true) {
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...
1167 58
            $this->file = $mapping['fieldName'];
1168 58
            $mapping['name'] = 'file';
1169 58
        }
1170 865 View Code Duplication
        if (isset($mapping['distance']) && $mapping['distance'] === true) {
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...
1171 7
            $this->distance = $mapping['fieldName'];
1172 7
        }
1173 865
        if (isset($mapping['id']) && $mapping['id'] === true) {
1174 842
            $mapping['name'] = '_id';
1175 842
            $this->identifier = $mapping['fieldName'];
1176 842 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...
1177 827
                $this->generatorType = constant(ClassMetadata::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
1178 827
            }
1179 842
            $this->generatorOptions = isset($mapping['options']) ? $mapping['options'] : array();
1180 842
            switch ($this->generatorType) {
1181 842
                case self::GENERATOR_TYPE_AUTO:
1182 775
                    $mapping['type'] = 'id';
1183 775
                    break;
1184 135
                default:
1185 135
                    if ( ! empty($this->generatorOptions['type'])) {
1186 38
                        $mapping['type'] = $this->generatorOptions['type'];
1187 135
                    } elseif (empty($mapping['type'])) {
1188 74
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
1189 74
                    }
1190 842
            }
1191 842
            unset($this->generatorOptions['type']);
1192 842
        }
1193
1194 865
        if ( ! isset($mapping['nullable'])) {
1195 42
            $mapping['nullable'] = false;
1196 42
        }
1197
1198
        // Synchronize the "simple" and "storeAs" mapping information for backwards compatibility
1199 865
        if (isset($mapping['simple']) && ($mapping['simple'] === true || $mapping['simple'] === 'true')) {
1200 261
            $mapping['storeAs'] = ClassMetadataInfo::REFERENCE_STORE_AS_ID;
1201 261
        }
1202
        // Remove the "simple" mapping and use "storeAs" in all further logic
1203 865
        if (isset($mapping['simple'])) {
1204 525
            unset($mapping['simple']);
1205 525
        }
1206
1207 865
        if (isset($mapping['reference'])
1208 865
            && isset($mapping['storeAs'])
1209 865
            && $mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID
1210 865
            && ! isset($mapping['targetDocument'])
1211 865
        ) {
1212 3
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
1213
        }
1214
1215 862
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
1216 862
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
1217 4
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
1218
        }
1219
1220 858
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && CollectionHelper::isAtomic($mapping['strategy'])) {
1221 1
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
1222
        }
1223
1224 857 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...
1225 463
            $mapping['association'] = self::REFERENCE_ONE;
1226 463
        }
1227 857 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...
1228 414
            $mapping['association'] = self::REFERENCE_MANY;
1229 414
        }
1230 857 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...
1231 415
            $mapping['association'] = self::EMBED_ONE;
1232 415
        }
1233 857 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...
1234 471
            $mapping['association'] = self::EMBED_MANY;
1235 471
        }
1236
1237 857
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
1238 99
            $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
1239 99
        }
1240
1241
        /*
1242
        if (isset($mapping['type']) && ($mapping['type'] === 'one' || $mapping['type'] === 'many')) {
1243
            $mapping['type'] = $mapping['type'] === 'one' ? self::ONE : self::MANY;
1244
        }
1245
        */
1246 857
        if (isset($mapping['version'])) {
1247 93
            $mapping['notSaved'] = true;
1248 93
            $this->setVersionMapping($mapping);
1249 92
        }
1250 857
        if (isset($mapping['lock'])) {
1251 26
            $mapping['notSaved'] = true;
1252 26
            $this->setLockMapping($mapping);
1253 25
        }
1254 857
        $mapping['isOwningSide'] = true;
1255 857
        $mapping['isInverseSide'] = false;
1256 857
        if (isset($mapping['reference'])) {
1257 531 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...
1258 220
                $mapping['isOwningSide'] = true;
1259 220
                $mapping['isInverseSide'] = false;
1260 220
            }
1261 531 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...
1262 269
                $mapping['isInverseSide'] = true;
1263 269
                $mapping['isOwningSide'] = false;
1264 269
            }
1265 531 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...
1266 201
                $mapping['isInverseSide'] = true;
1267 201
                $mapping['isOwningSide'] = false;
1268 201
            }
1269 531
            if (!isset($mapping['orphanRemoval'])) {
1270 511
                $mapping['orphanRemoval'] = false;
1271 511
            }
1272 531
        }
1273
1274 857
        $this->applyStorageStrategy($mapping);
1275
1276 856
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
1277 856
        if (isset($mapping['association'])) {
1278 672
            $this->associationMappings[$mapping['fieldName']] = $mapping;
1279 672
        }
1280
1281 856
        return $mapping;
1282
    }
1283
1284
    /**
1285
     * Validates the storage strategy of a mapping for consistency
1286
     * @param array $mapping
1287
     * @throws \Doctrine\ODM\MongoDB\Mapping\MappingException
1288
     */
1289 857
    private function applyStorageStrategy(array &$mapping)
1290
    {
1291 857
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1292 844
            return;
1293
        }
1294
1295 825
        switch (true) {
1296 825
            case $mapping['type'] == 'int':
1297 825
            case $mapping['type'] == 'float':
1298 825
            case $mapping['type'] == 'increment':
1299 303
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1300 303
                $allowedStrategies = [self::STORAGE_STRATEGY_SET, self::STORAGE_STRATEGY_INCREMENT];
1301 303
                break;
1302
1303 824
            case $mapping['type'] == 'many':
1304 561
                $defaultStrategy = CollectionHelper::DEFAULT_STRATEGY;
1305
                $allowedStrategies = [
1306 561
                    self::STORAGE_STRATEGY_PUSH_ALL,
1307 561
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1308 561
                    self::STORAGE_STRATEGY_SET,
1309 561
                    self::STORAGE_STRATEGY_SET_ARRAY,
1310 561
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1311 561
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1312 561
                ];
1313 561
                break;
1314
1315 817
            default:
1316 817
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1317 817
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1318 817
        }
1319
1320 825
        if (! isset($mapping['strategy'])) {
1321 818
            $mapping['strategy'] = $defaultStrategy;
1322 818
        }
1323
1324 825
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1325
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1326
        }
1327
1328 825
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1329 825
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1330 1
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1331
        }
1332 824
    }
1333
1334
    /**
1335
     * Map a MongoGridFSFile.
1336
     *
1337
     * @param array $mapping The mapping information.
1338
     */
1339
    public function mapFile(array $mapping)
1340
    {
1341
        $mapping['file'] = true;
1342
        $mapping['type'] = 'file';
1343
        $this->mapField($mapping);
1344
    }
1345
1346
    /**
1347
     * Map a single embedded document.
1348
     *
1349
     * @param array $mapping The mapping information.
1350
     */
1351 6
    public function mapOneEmbedded(array $mapping)
1352
    {
1353 6
        $mapping['embedded'] = true;
1354 6
        $mapping['type'] = 'one';
1355 6
        $this->mapField($mapping);
1356 5
    }
1357
1358
    /**
1359
     * Map a collection of embedded documents.
1360
     *
1361
     * @param array $mapping The mapping information.
1362
     */
1363 3
    public function mapManyEmbedded(array $mapping)
1364
    {
1365 3
        $mapping['embedded'] = true;
1366 3
        $mapping['type'] = 'many';
1367 3
        $this->mapField($mapping);
1368 3
    }
1369
1370
    /**
1371
     * Map a single document reference.
1372
     *
1373
     * @param array $mapping The mapping information.
1374
     */
1375 8
    public function mapOneReference(array $mapping)
1376
    {
1377 8
        $mapping['reference'] = true;
1378 8
        $mapping['type'] = 'one';
1379 8
        $this->mapField($mapping);
1380 8
    }
1381
1382
    /**
1383
     * Map a collection of document references.
1384
     *
1385
     * @param array $mapping The mapping information.
1386
     */
1387 8
    public function mapManyReference(array $mapping)
1388
    {
1389 8
        $mapping['reference'] = true;
1390 8
        $mapping['type'] = 'many';
1391 8
        $this->mapField($mapping);
1392 8
    }
1393
1394
    /**
1395
     * INTERNAL:
1396
     * Adds a field mapping without completing/validating it.
1397
     * This is mainly used to add inherited field mappings to derived classes.
1398
     *
1399
     * @param array $fieldMapping
1400
     */
1401 108
    public function addInheritedFieldMapping(array $fieldMapping)
1402
    {
1403 108
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1404
1405 108
        if (isset($fieldMapping['association'])) {
1406 76
            $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1407 76
        }
1408 108
    }
1409
1410
    /**
1411
     * INTERNAL:
1412
     * Adds an association mapping without completing/validating it.
1413
     * This is mainly used to add inherited association mappings to derived classes.
1414
     *
1415
     * @param array $mapping
1416
     *
1417
     * @return void
1418
     *
1419
     * @throws MappingException
1420
     */
1421 77
    public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
1422
    {
1423 77
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1424 77
    }
1425
1426
    /**
1427
     * Checks whether the class has a mapped association with the given field name.
1428
     *
1429
     * @param string $fieldName
1430
     * @return boolean
1431
     */
1432
    public function hasReference($fieldName)
1433
    {
1434
        return isset($this->fieldMappings[$fieldName]['reference']);
1435
    }
1436
1437
    /**
1438
     * Checks whether the class has a mapped embed with the given field name.
1439
     *
1440
     * @param string $fieldName
1441
     * @return boolean
1442
     */
1443
    public function hasEmbed($fieldName)
1444
    {
1445
        return isset($this->fieldMappings[$fieldName]['embedded']);
1446
    }
1447
1448
    /**
1449
     * {@inheritDoc}
1450
     *
1451
     * Checks whether the class has a mapped association (embed or reference) with the given field name.
1452
     */
1453
    public function hasAssociation($fieldName)
1454
    {
1455
        return $this->hasReference($fieldName) || $this->hasEmbed($fieldName);
1456
    }
1457
1458
    /**
1459
     * {@inheritDoc}
1460
     *
1461
     * Checks whether the class has a mapped reference or embed for the specified field and
1462
     * is a single valued association.
1463
     */
1464
    public function isSingleValuedAssociation($fieldName)
1465
    {
1466
        return $this->isSingleValuedReference($fieldName) || $this->isSingleValuedEmbed($fieldName);
1467
    }
1468
1469
    /**
1470
     * {@inheritDoc}
1471
     *
1472
     * Checks whether the class has a mapped reference or embed for the specified field and
1473
     * is a collection valued association.
1474
     */
1475
    public function isCollectionValuedAssociation($fieldName)
1476
    {
1477
        return $this->isCollectionValuedReference($fieldName) || $this->isCollectionValuedEmbed($fieldName);
1478
    }
1479
1480
    /**
1481
     * Checks whether the class has a mapped association for the specified field
1482
     * and if yes, checks whether it is a single-valued association (to-one).
1483
     *
1484
     * @param string $fieldName
1485
     * @return boolean TRUE if the association exists and is single-valued, FALSE otherwise.
1486
     */
1487
    public function isSingleValuedReference($fieldName)
1488
    {
1489
        return isset($this->fieldMappings[$fieldName]['association']) &&
1490
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_ONE;
1491
    }
1492
1493
    /**
1494
     * Checks whether the class has a mapped association for the specified field
1495
     * and if yes, checks whether it is a collection-valued association (to-many).
1496
     *
1497
     * @param string $fieldName
1498
     * @return boolean TRUE if the association exists and is collection-valued, FALSE otherwise.
1499
     */
1500
    public function isCollectionValuedReference($fieldName)
1501
    {
1502
        return isset($this->fieldMappings[$fieldName]['association']) &&
1503
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_MANY;
1504
    }
1505
1506
    /**
1507
     * Checks whether the class has a mapped embedded document for the specified field
1508
     * and if yes, checks whether it is a single-valued association (to-one).
1509
     *
1510
     * @param string $fieldName
1511
     * @return boolean TRUE if the association exists and is single-valued, FALSE otherwise.
1512
     */
1513
    public function isSingleValuedEmbed($fieldName)
1514
    {
1515
        return isset($this->fieldMappings[$fieldName]['association']) &&
1516
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_ONE;
1517
    }
1518
1519
    /**
1520
     * Checks whether the class has a mapped embedded document for the specified field
1521
     * and if yes, checks whether it is a collection-valued association (to-many).
1522
     *
1523
     * @param string $fieldName
1524
     * @return boolean TRUE if the association exists and is collection-valued, FALSE otherwise.
1525
     */
1526
    public function isCollectionValuedEmbed($fieldName)
1527
    {
1528
        return isset($this->fieldMappings[$fieldName]['association']) &&
1529
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_MANY;
1530
    }
1531
1532
    /**
1533
     * Sets the ID generator used to generate IDs for instances of this class.
1534
     *
1535
     * @param \Doctrine\ODM\MongoDB\Id\AbstractIdGenerator $generator
1536
     */
1537 770
    public function setIdGenerator($generator)
1538
    {
1539 770
        $this->idGenerator = $generator;
1540 770
    }
1541
1542
    /**
1543
     * Casts the identifier to its portable PHP type.
1544
     *
1545
     * @param mixed $id
1546
     * @return mixed $id
1547
     */
1548 499
    public function getPHPIdentifierValue($id)
1549
    {
1550 499
        $idType = $this->fieldMappings[$this->identifier]['type'];
1551 499
        return Type::getType($idType)->convertToPHPValue($id);
1552
    }
1553
1554
    /**
1555
     * Casts the identifier to its database type.
1556
     *
1557
     * @param mixed $id
1558
     * @return mixed $id
1559
     */
1560 573
    public function getDatabaseIdentifierValue($id)
1561
    {
1562 573
        $idType = $this->fieldMappings[$this->identifier]['type'];
1563 573
        return Type::getType($idType)->convertToDatabaseValue($id);
1564
    }
1565
1566
    /**
1567
     * Sets the document identifier of a document.
1568
     *
1569
     * The value will be converted to a PHP type before being set.
1570
     *
1571
     * @param object $document
1572
     * @param mixed $id
1573
     */
1574 498
    public function setIdentifierValue($document, $id)
1575
    {
1576 498
        $id = $this->getPHPIdentifierValue($id);
1577 498
        $this->reflFields[$this->identifier]->setValue($document, $id);
1578 498
    }
1579
1580
    /**
1581
     * Gets the document identifier as a PHP type.
1582
     *
1583
     * @param object $document
1584
     * @return mixed $id
1585
     */
1586 576
    public function getIdentifierValue($document)
1587
    {
1588 576
        return $this->reflFields[$this->identifier]->getValue($document);
1589
    }
1590
1591
    /**
1592
     * {@inheritDoc}
1593
     *
1594
     * Since MongoDB only allows exactly one identifier field this is a proxy
1595
     * to {@see getIdentifierValue()} and returns an array with the identifier
1596
     * field as a key.
1597
     */
1598
    public function getIdentifierValues($object)
1599
    {
1600
        return array($this->identifier => $this->getIdentifierValue($object));
1601
    }
1602
1603
    /**
1604
     * Get the document identifier object as a database type.
1605
     *
1606
     * @param object $document
1607
     *
1608
     * @return \MongoId $id The MongoID object.
1609
     */
1610 5
    public function getIdentifierObject($document)
1611
    {
1612 5
        return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));
1613
    }
1614
1615
    /**
1616
     * Sets the specified field to the specified value on the given document.
1617
     *
1618
     * @param object $document
1619
     * @param string $field
1620
     * @param mixed $value
1621
     */
1622 6
    public function setFieldValue($document, $field, $value)
1623
    {
1624 6
        if ($document instanceof Proxy && ! $document->__isInitialized()) {
1625
            //property changes to an uninitialized proxy will not be tracked or persisted,
1626
            //so the proxy needs to be loaded first.
1627
            $document->__load();
1628
        }
1629
1630 6
        $this->reflFields[$field]->setValue($document, $value);
1631 6
    }
1632
1633
    /**
1634
     * Gets the specified field's value off the given document.
1635
     *
1636
     * @param object $document
1637
     * @param string $field
1638
     *
1639
     * @return mixed
1640
     */
1641 1
    public function getFieldValue($document, $field)
1642
    {
1643 1
        if ($document instanceof Proxy && $field !== $this->identifier && ! $document->__isInitialized()) {
1644
            $document->__load();
1645
        }
1646
1647 1
        return $this->reflFields[$field]->getValue($document);
1648
    }
1649
1650
    /**
1651
     * Gets the mapping of a field.
1652
     *
1653
     * @param string $fieldName  The field name.
1654
     *
1655
     * @return array  The field mapping.
1656
     *
1657
     * @throws MappingException if the $fieldName is not found in the fieldMappings array
1658
     *
1659
     * @throws MappingException
1660
     */
1661 9
    public function getFieldMapping($fieldName)
1662
    {
1663 9
        if ( ! isset($this->fieldMappings[$fieldName])) {
1664
            throw MappingException::mappingNotFound($this->name, $fieldName);
1665
        }
1666 9
        return $this->fieldMappings[$fieldName];
1667
    }
1668
1669
    /**
1670
     * Gets mappings of fields holding embedded document(s).
1671
     *
1672
     * @return array of field mappings
1673
     */
1674
    public function getEmbeddedFieldsMappings()
1675
    {
1676
        return array_filter(
1677
            $this->associationMappings,
1678
            function($assoc) { return ! empty($assoc['embedded']); }
1679
        );
1680
    }
1681
1682
    /**
1683
     * Check if the field is not null.
1684
     *
1685
     * @param string $fieldName  The field name
1686
     *
1687
     * @return boolean  TRUE if the field is not null, FALSE otherwise.
1688
     */
1689 1
    public function isNullable($fieldName)
1690
    {
1691 1
        $mapping = $this->getFieldMapping($fieldName);
1692 1
        if ($mapping !== false) {
1693 1
            return isset($mapping['nullable']) && $mapping['nullable'] == true;
1694
        }
1695
        return false;
1696
    }
1697
1698
    /**
1699
     * Checks whether the document has a discriminator field and value configured.
1700
     *
1701
     * @return boolean
1702
     */
1703
    public function hasDiscriminator()
1704
    {
1705
        return isset($this->discriminatorField, $this->discriminatorValue);
1706
    }
1707
1708
    /**
1709
     * Sets the type of Id generator to use for the mapped class.
1710
     *
1711
     * @param string $generatorType Generator type.
1712
     */
1713 337
    public function setIdGeneratorType($generatorType)
1714
    {
1715 337
        $this->generatorType = $generatorType;
1716 337
    }
1717
1718
    /**
1719
     * Sets the Id generator options.
1720
     *
1721
     * @param array $generatorOptions Generator options.
1722
     */
1723
    public function setIdGeneratorOptions($generatorOptions)
1724
    {
1725
        $this->generatorOptions = $generatorOptions;
1726
    }
1727
1728
    /**
1729
     * @return boolean
1730
     */
1731 543
    public function isInheritanceTypeNone()
1732
    {
1733 543
        return $this->inheritanceType == self::INHERITANCE_TYPE_NONE;
1734
    }
1735
1736
    /**
1737
     * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.
1738
     *
1739
     * @return boolean
1740
     */
1741 331
    public function isInheritanceTypeSingleCollection()
1742
    {
1743 331
        return $this->inheritanceType == self::INHERITANCE_TYPE_SINGLE_COLLECTION;
1744
    }
1745
1746
    /**
1747
     * Checks whether the mapped class uses the COLLECTION_PER_CLASS inheritance mapping strategy.
1748
     *
1749
     * @return boolean
1750
     */
1751
    public function isInheritanceTypeCollectionPerClass()
1752
    {
1753
        return $this->inheritanceType == self::INHERITANCE_TYPE_COLLECTION_PER_CLASS;
1754
    }
1755
1756
    /**
1757
     * Sets the mapped subclasses of this class.
1758
     *
1759
     * @param string[] $subclasses The names of all mapped subclasses.
1760
     */
1761 2
    public function setSubclasses(array $subclasses)
1762
    {
1763 2
        foreach ($subclasses as $subclass) {
1764 2
            if (strpos($subclass, '\\') === false && strlen($this->namespace)) {
1765 1
                $this->subClasses[] = $this->namespace . '\\' . $subclass;
1766 1
            } else {
1767 1
                $this->subClasses[] = $subclass;
1768
            }
1769 2
        }
1770 2
    }
1771
1772
    /**
1773
     * Sets the parent class names.
1774
     * Assumes that the class names in the passed array are in the order:
1775
     * directParent -> directParentParent -> directParentParentParent ... -> root.
1776
     *
1777
     * @param string[] $classNames
1778
     */
1779 825
    public function setParentClasses(array $classNames)
1780
    {
1781 825
        $this->parentClasses = $classNames;
1782
1783 825
        if (count($classNames) > 0) {
1784 94
            $this->rootDocumentName = array_pop($classNames);
1785 94
        }
1786 825
    }
1787
1788
    /**
1789
     * Checks whether the class will generate a new \MongoId instance for us.
1790
     *
1791
     * @return boolean TRUE if the class uses the AUTO generator, FALSE otherwise.
1792
     */
1793
    public function isIdGeneratorAuto()
1794
    {
1795
        return $this->generatorType == self::GENERATOR_TYPE_AUTO;
1796
    }
1797
1798
    /**
1799
     * Checks whether the class will use a collection to generate incremented identifiers.
1800
     *
1801
     * @return boolean TRUE if the class uses the INCREMENT generator, FALSE otherwise.
1802
     */
1803
    public function isIdGeneratorIncrement()
1804
    {
1805
        return $this->generatorType == self::GENERATOR_TYPE_INCREMENT;
1806
    }
1807
1808
    /**
1809
     * Checks whether the class will generate a uuid id.
1810
     *
1811
     * @return boolean TRUE if the class uses the UUID generator, FALSE otherwise.
1812
     */
1813
    public function isIdGeneratorUuid()
1814
    {
1815
        return $this->generatorType == self::GENERATOR_TYPE_UUID;
1816
    }
1817
1818
    /**
1819
     * Checks whether the class uses no id generator.
1820
     *
1821
     * @return boolean TRUE if the class does not use any id generator, FALSE otherwise.
1822
     */
1823
    public function isIdGeneratorNone()
1824
    {
1825
        return $this->generatorType == self::GENERATOR_TYPE_NONE;
1826
    }
1827
1828
    /**
1829
     * Sets the version field mapping used for versioning. Sets the default
1830
     * value to use depending on the column type.
1831
     *
1832
     * @param array $mapping   The version field mapping array
1833
     *
1834
     * @throws LockException
1835
     */
1836 93
    public function setVersionMapping(array &$mapping)
1837
    {
1838 93
        if ($mapping['type'] !== 'int' && $mapping['type'] !== 'date') {
1839 1
            throw LockException::invalidVersionFieldType($mapping['type']);
1840
        }
1841
1842 92
        $this->isVersioned  = true;
1843 92
        $this->versionField = $mapping['fieldName'];
1844 92
    }
1845
1846
    /**
1847
     * Sets whether this class is to be versioned for optimistic locking.
1848
     *
1849
     * @param boolean $bool
1850
     */
1851 331
    public function setVersioned($bool)
1852
    {
1853 331
        $this->isVersioned = $bool;
1854 331
    }
1855
1856
    /**
1857
     * Sets the name of the field that is to be used for versioning if this class is
1858
     * versioned for optimistic locking.
1859
     *
1860
     * @param string $versionField
1861
     */
1862 331
    public function setVersionField($versionField)
1863
    {
1864 331
        $this->versionField = $versionField;
1865 331
    }
1866
1867
    /**
1868
     * Sets the version field mapping used for versioning. Sets the default
1869
     * value to use depending on the column type.
1870
     *
1871
     * @param array $mapping   The version field mapping array
1872
     *
1873
     * @throws \Doctrine\ODM\MongoDB\LockException
1874
     */
1875 26
    public function setLockMapping(array &$mapping)
1876
    {
1877 26
        if ($mapping['type'] !== 'int') {
1878 1
            throw LockException::invalidLockFieldType($mapping['type']);
1879
        }
1880
1881 25
        $this->isLockable = true;
1882 25
        $this->lockField = $mapping['fieldName'];
1883 25
    }
1884
1885
    /**
1886
     * Sets whether this class is to allow pessimistic locking.
1887
     *
1888
     * @param boolean $bool
1889
     */
1890
    public function setLockable($bool)
1891
    {
1892
        $this->isLockable = $bool;
1893
    }
1894
1895
    /**
1896
     * Sets the name of the field that is to be used for storing whether a document
1897
     * is currently locked or not.
1898
     *
1899
     * @param string $lockField
1900
     */
1901
    public function setLockField($lockField)
1902
    {
1903
        $this->lockField = $lockField;
1904
    }
1905
1906
    /**
1907
     * {@inheritDoc}
1908
     */
1909
    public function getFieldNames()
1910
    {
1911
        return array_keys($this->fieldMappings);
1912
    }
1913
1914
    /**
1915
     * {@inheritDoc}
1916
     */
1917
    public function getAssociationNames()
1918
    {
1919
        return array_keys($this->associationMappings);
1920
    }
1921
1922
    /**
1923
     * {@inheritDoc}
1924
     */
1925
    public function getTypeOfField($fieldName)
1926
    {
1927
        return isset($this->fieldMappings[$fieldName]) ?
1928
            $this->fieldMappings[$fieldName]['type'] : null;
1929
    }
1930
1931
    /**
1932
     * {@inheritDoc}
1933
     */
1934
    public function getAssociationTargetClass($assocName)
1935
    {
1936
        if ( ! isset($this->associationMappings[$assocName])) {
1937
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1938
        }
1939
1940
        return $this->associationMappings[$assocName]['targetDocument'];
1941
    }
1942
1943
    /**
1944
     * {@inheritDoc}
1945
     */
1946
    public function isAssociationInverseSide($fieldName)
1947
    {
1948
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1949
    }
1950
1951
    /**
1952
     * {@inheritDoc}
1953
     */
1954
    public function getAssociationMappedByTargetField($fieldName)
1955
    {
1956
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1957
    }
1958
}
1959