Completed
Push — master ( 80e8df...60de96 )
by Maciej
18s
created

Doctrine/ODM/MongoDB/Mapping/ClassMetadataInfo.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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: Describes the level of acknowledgement requested from MongoDB for write operations.
193
     */
194
    public $writeConcern;
195
196
    /**
197
     * READ-ONLY: The field name of the document identifier.
198
     */
199
    public $identifier;
200
201
    /**
202
     * READ-ONLY: The field that stores a file reference and indicates the
203
     * document is a file and should be stored on the MongoGridFS.
204
     */
205
    public $file;
206
207
    /**
208
     * READ-ONLY: The field that stores the calculated distance when performing geo spatial
209
     * queries.
210
     */
211
    public $distance;
212
213
    /**
214
     * READ-ONLY: Whether or not reads for this class are okay to read from a slave.
215
     */
216
    public $slaveOkay;
217
218
    /**
219
     * READ-ONLY: The array of indexes for the document collection.
220
     */
221
    public $indexes = array();
222
223
    /**
224
     * READ-ONLY: Keys and options describing shard key. Only for sharded collections.
225
     */
226
    public $shardKey;
227
228
    /**
229
     * READ-ONLY: Whether or not queries on this document should require indexes.
230
     *
231
     * @deprecated property was deprecated in 1.2 and will be removed in 2.0
232
     */
233
    public $requireIndexes = false;
234
235
    /**
236
     * READ-ONLY: The name of the document class.
237
     */
238
    public $name;
239
240
    /**
241
     * READ-ONLY: The namespace the document class is contained in.
242
     *
243
     * @var string
244
     * @todo Not really needed. Usage could be localized.
245
     */
246
    public $namespace;
247
248
    /**
249
     * READ-ONLY: The name of the document class that is at the root of the mapped document inheritance
250
     * hierarchy. If the document is not part of a mapped inheritance hierarchy this is the same
251
     * as {@link $documentName}.
252
     *
253
     * @var string
254
     */
255
    public $rootDocumentName;
256
257
    /**
258
     * The name of the custom repository class used for the document class.
259
     * (Optional).
260
     *
261
     * @var string
262
     */
263
    public $customRepositoryClassName;
264
265
    /**
266
     * READ-ONLY: The names of the parent classes (ancestors).
267
     *
268
     * @var array
269
     */
270
    public $parentClasses = array();
271
272
    /**
273
     * READ-ONLY: The names of all subclasses (descendants).
274
     *
275
     * @var array
276
     */
277
    public $subClasses = array();
278
279
    /**
280
     * The ReflectionProperty instances of the mapped class.
281
     *
282
     * @var \ReflectionProperty[]
283
     */
284
    public $reflFields = array();
285
286
    /**
287
     * READ-ONLY: The inheritance mapping type used by the class.
288
     *
289
     * @var integer
290
     */
291
    public $inheritanceType = self::INHERITANCE_TYPE_NONE;
292
293
    /**
294
     * READ-ONLY: The Id generator type used by the class.
295
     *
296
     * @var string
297
     */
298
    public $generatorType = self::GENERATOR_TYPE_AUTO;
299
300
    /**
301
     * READ-ONLY: The Id generator options.
302
     *
303
     * @var array
304
     */
305
    public $generatorOptions = array();
306
307
    /**
308
     * READ-ONLY: The ID generator used for generating IDs for this class.
309
     *
310
     * @var \Doctrine\ODM\MongoDB\Id\AbstractIdGenerator
311
     */
312
    public $idGenerator;
313
314
    /**
315
     * READ-ONLY: The field mappings of the class.
316
     * Keys are field names and values are mapping definitions.
317
     *
318
     * The mapping definition array has the following values:
319
     *
320
     * - <b>fieldName</b> (string)
321
     * The name of the field in the Document.
322
     *
323
     * - <b>id</b> (boolean, optional)
324
     * Marks the field as the primary key of the document. Multiple fields of an
325
     * document can have the id attribute, forming a composite key.
326
     *
327
     * @var array
328
     */
329
    public $fieldMappings = array();
330
331
    /**
332
     * READ-ONLY: The association mappings of the class.
333
     * Keys are field names and values are mapping definitions.
334
     *
335
     * @var array
336
     */
337
    public $associationMappings = array();
338
339
    /**
340
     * READ-ONLY: Array of fields to also load with a given method.
341
     *
342
     * @var array
343
     */
344
    public $alsoLoadMethods = array();
345
346
    /**
347
     * READ-ONLY: The registered lifecycle callbacks for documents of this class.
348
     *
349
     * @var array
350
     */
351
    public $lifecycleCallbacks = array();
352
353
    /**
354
     * READ-ONLY: The discriminator value of this class.
355
     *
356
     * <b>This does only apply to the JOINED and SINGLE_COLLECTION inheritance mapping strategies
357
     * where a discriminator field is used.</b>
358
     *
359
     * @var mixed
360
     * @see discriminatorField
361
     */
362
    public $discriminatorValue;
363
364
    /**
365
     * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
366
     *
367
     * <b>This does only apply to the SINGLE_COLLECTION inheritance mapping strategy
368
     * where a discriminator field is used.</b>
369
     *
370
     * @var mixed
371
     * @see discriminatorField
372
     */
373
    public $discriminatorMap = array();
374
375
    /**
376
     * READ-ONLY: The definition of the discriminator field used in SINGLE_COLLECTION
377
     * inheritance mapping.
378
     *
379
     * @var string
380
     */
381
    public $discriminatorField;
382
383
    /**
384
     * READ-ONLY: The default value for discriminatorField in case it's not set in the document
385
     *
386
     * @var string
387
     * @see discriminatorField
388
     */
389
    public $defaultDiscriminatorValue;
390
391
    /**
392
     * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
393
     *
394
     * @var boolean
395
     */
396
    public $isMappedSuperclass = false;
397
398
    /**
399
     * READ-ONLY: Whether this class describes the mapping of a embedded document.
400
     *
401
     * @var boolean
402
     */
403
    public $isEmbeddedDocument = false;
404
405
    /**
406
     * READ-ONLY: Whether this class describes the mapping of an aggregation result document.
407
     *
408
     * @var boolean
409
     */
410
    public $isQueryResultDocument = false;
411
412
    /**
413
     * READ-ONLY: The policy used for change-tracking on entities of this class.
414
     *
415
     * @var integer
416
     */
417
    public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;
418
419
    /**
420
     * READ-ONLY: A flag for whether or not instances of this class are to be versioned
421
     * with optimistic locking.
422
     *
423
     * @var boolean $isVersioned
424
     */
425
    public $isVersioned;
426
427
    /**
428
     * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
429
     *
430
     * @var mixed $versionField
431
     */
432
    public $versionField;
433
434
    /**
435
     * READ-ONLY: A flag for whether or not instances of this class are to allow pessimistic
436
     * locking.
437
     *
438
     * @var boolean $isLockable
439
     */
440
    public $isLockable;
441
442
    /**
443
     * READ-ONLY: The name of the field which is used for locking a document.
444
     *
445
     * @var mixed $lockField
446
     */
447
    public $lockField;
448
449
    /**
450
     * The ReflectionClass instance of the mapped class.
451
     *
452
     * @var \ReflectionClass
453
     */
454
    public $reflClass;
455
456
    /**
457
     * Initializes a new ClassMetadata instance that will hold the object-document mapping
458
     * metadata of the class with the given name.
459
     *
460
     * @param string $documentName The name of the document class the new instance is used for.
461
     */
462 975
    public function __construct($documentName)
463
    {
464 975
        $this->name = $documentName;
465 975
        $this->rootDocumentName = $documentName;
466 975
    }
467
468
    /**
469
     * {@inheritDoc}
470
     */
471 903
    public function getReflectionClass()
472
    {
473 903
        if ( ! $this->reflClass) {
474 2
            $this->reflClass = new \ReflectionClass($this->name);
475
        }
476
477 903
        return $this->reflClass;
478
    }
479
480
    /**
481
     * {@inheritDoc}
482
     */
483 330
    public function isIdentifier($fieldName)
484
    {
485 330
        return $this->identifier === $fieldName;
486
    }
487
488
    /**
489
     * INTERNAL:
490
     * Sets the mapped identifier field of this class.
491
     *
492
     * @param string $identifier
493
     */
494 370
    public function setIdentifier($identifier)
495
    {
496 370
        $this->identifier = $identifier;
497 370
    }
498
499
    /**
500
     * {@inheritDoc}
501
     *
502
     * Since MongoDB only allows exactly one identifier field
503
     * this will always return an array with only one value
504
     */
505 40
    public function getIdentifier()
506
    {
507 40
        return array($this->identifier);
508
    }
509
510
    /**
511
     * {@inheritDoc}
512
     *
513
     * Since MongoDB only allows exactly one identifier field
514
     * this will always return an array with only one value
515
     */
516 98
    public function getIdentifierFieldNames()
517
    {
518 98
        return array($this->identifier);
519
    }
520
521
    /**
522
     * {@inheritDoc}
523
     */
524 561
    public function hasField($fieldName)
525
    {
526 561
        return isset($this->fieldMappings[$fieldName]);
527
    }
528
529
    /**
530
     * Sets the inheritance type used by the class and it's subclasses.
531
     *
532
     * @param integer $type
533
     */
534 386
    public function setInheritanceType($type)
535
    {
536 386
        $this->inheritanceType = $type;
537 386
    }
538
539
    /**
540
     * Checks whether a mapped field is inherited from an entity superclass.
541
     *
542
     * @param  string $fieldName
543
     *
544
     * @return boolean TRUE if the field is inherited, FALSE otherwise.
545
     */
546 903
    public function isInheritedField($fieldName)
547
    {
548 903
        return isset($this->fieldMappings[$fieldName]['inherited']);
549
    }
550
551
    /**
552
     * Registers a custom repository class for the document class.
553
     *
554
     * @param string $repositoryClassName The class name of the custom repository.
555
     */
556 318
    public function setCustomRepositoryClass($repositoryClassName)
557
    {
558 318
        if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
559
            return;
560
        }
561
562 318 View Code Duplication
        if ($repositoryClassName && strpos($repositoryClassName, '\\') === false && strlen($this->namespace)) {
563 4
            $repositoryClassName = $this->namespace . '\\' . $repositoryClassName;
564
        }
565
566 318
        $this->customRepositoryClassName = $repositoryClassName;
567 318
    }
568
569
    /**
570
     * Dispatches the lifecycle event of the given document by invoking all
571
     * registered callbacks.
572
     *
573
     * @param string $event     Lifecycle event
574
     * @param object $document  Document on which the event occurred
575
     * @param array  $arguments Arguments to pass to all callbacks
576
     * @throws \InvalidArgumentException if document class is not this class or
577
     *                                   a Proxy of this class
578
     */
579 660
    public function invokeLifecycleCallbacks($event, $document, array $arguments = null)
580
    {
581 660
        if ( ! $document instanceof $this->name) {
582 1
            throw new \InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"', $this->name, get_class($document)));
583
        }
584
585 659
        if (empty($this->lifecycleCallbacks[$event])) {
586 645
            return;
587
        }
588
589 196
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
590 196
            if ($arguments !== null) {
591 195
                call_user_func_array(array($document, $callback), $arguments);
592
            } else {
593 2
                $document->$callback();
594
            }
595
        }
596 196
    }
597
598
    /**
599
     * Checks whether the class has callbacks registered for a lifecycle event.
600
     *
601
     * @param string $event Lifecycle event
602
     *
603
     * @return boolean
604
     */
605
    public function hasLifecycleCallbacks($event)
606
    {
607
        return ! empty($this->lifecycleCallbacks[$event]);
608
    }
609
610
    /**
611
     * Gets the registered lifecycle callbacks for an event.
612
     *
613
     * @param string $event
614
     * @return array
615
     */
616
    public function getLifecycleCallbacks($event)
617
    {
618
        return isset($this->lifecycleCallbacks[$event]) ? $this->lifecycleCallbacks[$event] : array();
619
    }
620
621
    /**
622
     * Adds a lifecycle callback for documents of this class.
623
     *
624
     * If the callback is already registered, this is a NOOP.
625
     *
626
     * @param string $callback
627
     * @param string $event
628
     */
629 297
    public function addLifecycleCallback($callback, $event)
630
    {
631 297
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
632 1
            return;
633
        }
634
635 297
        $this->lifecycleCallbacks[$event][] = $callback;
636 297
    }
637
638
    /**
639
     * Sets the lifecycle callbacks for documents of this class.
640
     *
641
     * Any previously registered callbacks are overwritten.
642
     *
643
     * @param array $callbacks
644
     */
645 369
    public function setLifecycleCallbacks(array $callbacks)
646
    {
647 369
        $this->lifecycleCallbacks = $callbacks;
648 369
    }
649
650
    /**
651
     * Registers a method for loading document data before field hydration.
652
     *
653
     * Note: A method may be registered multiple times for different fields.
654
     * it will be invoked only once for the first field found.
655
     *
656
     * @param string       $method Method name
657
     * @param array|string $fields Database field name(s)
658
     */
659 15
    public function registerAlsoLoadMethod($method, $fields)
660
    {
661 15
        $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : array($fields);
662 15
    }
663
664
    /**
665
     * Sets the AlsoLoad methods for documents of this class.
666
     *
667
     * Any previously registered methods are overwritten.
668
     *
669
     * @param array $methods
670
     */
671 369
    public function setAlsoLoadMethods(array $methods)
672
    {
673 369
        $this->alsoLoadMethods = $methods;
674 369
    }
675
676
    /**
677
     * Sets the discriminator field.
678
     *
679
     * The field name is the the unmapped database field. Discriminator values
680
     * are only used to discern the hydration class and are not mapped to class
681
     * properties.
682
     *
683
     * @param string $discriminatorField
684
     *
685
     * @throws MappingException If the discriminator field conflicts with the
686
     *                          "name" attribute of a mapped field.
687
     */
688 399
    public function setDiscriminatorField($discriminatorField)
689
    {
690 399
        if ($discriminatorField === null) {
691 326
            $this->discriminatorField = null;
692
693 326
            return;
694
        }
695
696
        // Handle array argument with name/fieldName keys for BC
697 130
        if (is_array($discriminatorField)) {
698
            if (isset($discriminatorField['name'])) {
699
                $discriminatorField = $discriminatorField['name'];
700
            } elseif (isset($discriminatorField['fieldName'])) {
701
                $discriminatorField = $discriminatorField['fieldName'];
702
            }
703
        }
704
705 130
        foreach ($this->fieldMappings as $fieldMapping) {
706 4
            if ($discriminatorField == $fieldMapping['name']) {
707 1
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
708
            }
709
        }
710
711 129
        $this->discriminatorField = $discriminatorField;
712 129
    }
713
714
    /**
715
     * Sets the discriminator values used by this class.
716
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
717
     *
718
     * @param array $map
719
     *
720
     * @throws MappingException
721
     */
722 392
    public function setDiscriminatorMap(array $map)
723
    {
724 392
        foreach ($map as $value => $className) {
725 125
            if (strpos($className, '\\') === false && strlen($this->namespace)) {
726 91
                $className = $this->namespace . '\\' . $className;
727
            }
728 125
            $this->discriminatorMap[$value] = $className;
729 125
            if ($this->name == $className) {
730 117
                $this->discriminatorValue = $value;
731
            } else {
732 120
                if ( ! class_exists($className)) {
733
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
734
                }
735 120
                if (is_subclass_of($className, $this->name)) {
736 106
                    $this->subClasses[] = $className;
737
                }
738
            }
739
        }
740 392
    }
741
742
    /**
743
     * Sets the default discriminator value to be used for this class
744
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies if the document has no discriminator value
745
     *
746
     * @param string $defaultDiscriminatorValue
747
     *
748
     * @throws MappingException
749
     */
750 376
    public function setDefaultDiscriminatorValue($defaultDiscriminatorValue)
751
    {
752 376
        if ($defaultDiscriminatorValue === null) {
753 369
            $this->defaultDiscriminatorValue = null;
754
755 369
            return;
756
        }
757
758 60
        if (!array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
759
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
760
        }
761
762 60
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
763 60
    }
764
765
    /**
766
     * Sets the discriminator value for this class.
767
     * Used for JOINED/SINGLE_TABLE inheritance and multiple document types in a single
768
     * collection.
769
     *
770
     * @param string $value
771
     */
772 3
    public function setDiscriminatorValue($value)
773
    {
774 3
        $this->discriminatorMap[$value] = $this->name;
775 3
        $this->discriminatorValue = $value;
776 3
    }
777
778
    /**
779
     * Sets the slaveOkay option applied to collections for this class.
780
     *
781
     * @param boolean|null $slaveOkay
782
     */
783 3
    public function setSlaveOkay($slaveOkay)
784
    {
785 3
        $this->slaveOkay = $slaveOkay === null ? null : (boolean) $slaveOkay;
786 3
    }
787
788
    /**
789
     * Add a index for this Document.
790
     *
791
     * @param array $keys Array of keys for the index.
792
     * @param array $options Array of options for the index.
793
     */
794 232
    public function addIndex($keys, array $options = array())
795
    {
796 232
        $this->indexes[] = array(
797 View Code Duplication
            'keys' => array_map(function($value) {
798 232
                if ($value == 1 || $value == -1) {
799 63
                    return (int) $value;
800
                }
801 224
                if (is_string($value)) {
802 224
                    $lower = strtolower($value);
803 224
                    if ($lower === 'asc') {
804 217
                        return 1;
805 11
                    } elseif ($lower === 'desc') {
806 4
                        return -1;
807
                    }
808
                }
809 7
                return $value;
810 232
            }, $keys),
811 232
            'options' => $options
812
        );
813 232
    }
814
815
    /**
816
     * Set whether or not queries on this document should require indexes.
817
     *
818
     * @param bool $requireIndexes
819
     *
820
     * @deprecated method was deprecated in 1.2 and will be removed in 2.0
821
     */
822 894
    public function setRequireIndexes($requireIndexes)
823
    {
824 894
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
825 894
            'requireIndexes was deprecated in version 1.2 and will be removed altogether in 2.0.',
826 894
            E_USER_DEPRECATED
827
        );
828 894
        $this->requireIndexes = $requireIndexes;
829 894
    }
830
831
    /**
832
     * Returns the array of indexes for this Document.
833
     *
834
     * @return array $indexes The array of indexes.
835
     */
836 54
    public function getIndexes()
837
    {
838 54
        return $this->indexes;
839
    }
840
841
    /**
842
     * Checks whether this document has indexes or not.
843
     *
844
     * @return boolean
845
     */
846
    public function hasIndexes()
847
    {
848
        return $this->indexes ? true : false;
849
    }
850
851
    /**
852
     * Set shard key for this Document.
853
     *
854
     * @param array $keys Array of document keys.
855
     * @param array $options Array of sharding options.
856
     *
857
     * @throws MappingException
858
     */
859 77
    public function setShardKey(array $keys, array $options = array())
860
    {
861 77
        if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && !is_null($this->shardKey)) {
862 2
            throw MappingException::shardKeyInSingleCollInheritanceSubclass($this->getName());
863
        }
864
865 77
        if ($this->isEmbeddedDocument) {
866 2
            throw MappingException::embeddedDocumentCantHaveShardKey($this->getName());
867
        }
868
869 75
        foreach (array_keys($keys) as $field) {
870 75
            if (! isset($this->fieldMappings[$field])) {
871 68
                continue;
872
            }
873
874 7
            if (in_array($this->fieldMappings[$field]['type'], ['many', 'collection'])) {
875 3
                throw MappingException::noMultiKeyShardKeys($this->getName(), $field);
876
            }
877
878 4
            if ($this->fieldMappings[$field]['strategy'] !== static::STORAGE_STRATEGY_SET) {
879 1
                throw MappingException::onlySetStrategyAllowedInShardKey($this->getName(), $field);
880
            }
881
        }
882
883 71
        $this->shardKey = array(
884 View Code Duplication
            'keys' => array_map(function($value) {
885 71
                if ($value == 1 || $value == -1) {
886 6
                    return (int) $value;
887
                }
888 70
                if (is_string($value)) {
889 70
                    $lower = strtolower($value);
890 70
                    if ($lower === 'asc') {
891 69
                        return 1;
892 53
                    } elseif ($lower === 'desc') {
893
                        return -1;
894
                    }
895
                }
896 53
                return $value;
897 71
            }, $keys),
898 71
            'options' => $options
899
        );
900 71
    }
901
902
    /**
903
     * @return array
904
     */
905 18
    public function getShardKey()
906
    {
907 18
        return $this->shardKey;
908
    }
909
910
    /**
911
     * Checks whether this document has shard key or not.
912
     *
913
     * @return bool
914
     */
915 598
    public function isSharded()
916
    {
917 598
        return $this->shardKey ? true : false;
918
    }
919
920
    /**
921
     * Sets the write concern used by this class.
922
     *
923
     * @param string $writeConcern
924
     */
925 383
    public function setWriteConcern($writeConcern)
926
    {
927 383
        $this->writeConcern = $writeConcern;
928 383
    }
929
930
    /**
931
     * @return string
932
     */
933 12
    public function getWriteConcern()
934
    {
935 12
        return $this->writeConcern;
936
    }
937
938
    /**
939
     * Whether there is a write concern configured for this class.
940
     *
941
     * @return bool
942
     */
943 604
    public function hasWriteConcern()
944
    {
945 604
        return $this->writeConcern !== null;
946
    }
947
948
    /**
949
     * Sets the change tracking policy used by this class.
950
     *
951
     * @param integer $policy
952
     */
953 374
    public function setChangeTrackingPolicy($policy)
954
    {
955 374
        $this->changeTrackingPolicy = $policy;
956 374
    }
957
958
    /**
959
     * Whether the change tracking policy of this class is "deferred explicit".
960
     *
961
     * @return boolean
962
     */
963 73
    public function isChangeTrackingDeferredExplicit()
964
    {
965 73
        return $this->changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_EXPLICIT;
966
    }
967
968
    /**
969
     * Whether the change tracking policy of this class is "deferred implicit".
970
     *
971
     * @return boolean
972
     */
973 624
    public function isChangeTrackingDeferredImplicit()
974
    {
975 624
        return $this->changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_IMPLICIT;
976
    }
977
978
    /**
979
     * Whether the change tracking policy of this class is "notify".
980
     *
981
     * @return boolean
982
     */
983 347
    public function isChangeTrackingNotify()
984
    {
985 347
        return $this->changeTrackingPolicy == self::CHANGETRACKING_NOTIFY;
986
    }
987
988
    /**
989
     * Gets the ReflectionProperties of the mapped class.
990
     *
991
     * @return array An array of ReflectionProperty instances.
992
     */
993 98
    public function getReflectionProperties()
994
    {
995 98
        return $this->reflFields;
996
    }
997
998
    /**
999
     * Gets a ReflectionProperty for a specific field of the mapped class.
1000
     *
1001
     * @param string $name
1002
     *
1003
     * @return \ReflectionProperty
1004
     */
1005
    public function getReflectionProperty($name)
1006
    {
1007
        return $this->reflFields[$name];
1008
    }
1009
1010
    /**
1011
     * {@inheritDoc}
1012
     */
1013 909
    public function getName()
1014
    {
1015 909
        return $this->name;
1016
    }
1017
1018
    /**
1019
     * The namespace this Document class belongs to.
1020
     *
1021
     * @return string $namespace The namespace name.
1022
     */
1023
    public function getNamespace()
1024
    {
1025
        return $this->namespace;
1026
    }
1027
1028
    /**
1029
     * Returns the database this Document is mapped to.
1030
     *
1031
     * @return string $db The database name.
1032
     */
1033 829
    public function getDatabase()
1034
    {
1035 829
        return $this->db;
1036
    }
1037
1038
    /**
1039
     * Set the database this Document is mapped to.
1040
     *
1041
     * @param string $db The database name
1042
     */
1043 104
    public function setDatabase($db)
1044
    {
1045 104
        $this->db = $db;
1046 104
    }
1047
1048
    /**
1049
     * Get the collection this Document is mapped to.
1050
     *
1051
     * @return string $collection The collection name.
1052
     */
1053 834
    public function getCollection()
1054
    {
1055 834
        return $this->collection;
1056
    }
1057
1058
    /**
1059
     * Sets the collection this Document is mapped to.
1060
     *
1061
     * @param array|string $name
1062
     *
1063
     * @throws \InvalidArgumentException
1064
     */
1065 939
    public function setCollection($name)
1066
    {
1067 939
        if (is_array($name)) {
1068
            if ( ! isset($name['name'])) {
1069
                throw new \InvalidArgumentException('A name key is required when passing an array to setCollection()');
1070
            }
1071
            $this->collectionCapped = isset($name['capped']) ? $name['capped'] : false;
1072
            $this->collectionSize = isset($name['size']) ? $name['size'] : 0;
1073
            $this->collectionMax = isset($name['max']) ? $name['max'] : 0;
1074
            $this->collection = $name['name'];
1075
        } else {
1076 939
            $this->collection = $name;
1077
        }
1078 939
    }
1079
1080
    /**
1081
     * Get whether or not the documents collection is capped.
1082
     *
1083
     * @return boolean
1084
     */
1085 4
    public function getCollectionCapped()
1086
    {
1087 4
        return $this->collectionCapped;
1088
    }
1089
1090
    /**
1091
     * Set whether or not the documents collection is capped.
1092
     *
1093
     * @param boolean $bool
1094
     */
1095 1
    public function setCollectionCapped($bool)
1096
    {
1097 1
        $this->collectionCapped = $bool;
1098 1
    }
1099
1100
    /**
1101
     * Get the collection size
1102
     *
1103
     * @return integer
1104
     */
1105 4
    public function getCollectionSize()
1106
    {
1107 4
        return $this->collectionSize;
1108
    }
1109
1110
    /**
1111
     * Set the collection size.
1112
     *
1113
     * @param integer $size
1114
     */
1115 1
    public function setCollectionSize($size)
1116
    {
1117 1
        $this->collectionSize = $size;
1118 1
    }
1119
1120
    /**
1121
     * Get the collection max.
1122
     *
1123
     * @return integer
1124
     */
1125 4
    public function getCollectionMax()
1126
    {
1127 4
        return $this->collectionMax;
1128
    }
1129
1130
    /**
1131
     * Set the collection max.
1132
     *
1133
     * @param integer $max
1134
     */
1135 1
    public function setCollectionMax($max)
1136
    {
1137 1
        $this->collectionMax = $max;
1138 1
    }
1139
1140
    /**
1141
     * Returns TRUE if this Document is mapped to a collection FALSE otherwise.
1142
     *
1143
     * @return boolean
1144
     */
1145
    public function isMappedToCollection()
1146
    {
1147
        return $this->collection ? true : false;
1148
    }
1149
1150
    /**
1151
     * Returns TRUE if this Document is a file to be stored on the MongoGridFS FALSE otherwise.
1152
     *
1153
     * @return boolean
1154
     */
1155 775
    public function isFile()
1156
    {
1157 775
        return $this->file ? true : false;
1158
    }
1159
1160
    /**
1161
     * Returns the file field name.
1162
     *
1163
     * @return string $file The file field name.
1164
     */
1165 369
    public function getFile()
1166
    {
1167 369
        return $this->file;
1168
    }
1169
1170
    /**
1171
     * Set the field name that stores the grid file.
1172
     *
1173
     * @param string $file
1174
     */
1175 370
    public function setFile($file)
1176
    {
1177 370
        $this->file = $file;
1178 370
    }
1179
1180
    /**
1181
     * Returns the distance field name.
1182
     *
1183
     * @return string $distance The distance field name.
1184
     */
1185
    public function getDistance()
1186
    {
1187
        return $this->distance;
1188
    }
1189
1190
    /**
1191
     * Set the field name that stores the distance.
1192
     *
1193
     * @param string $distance
1194
     */
1195 1
    public function setDistance($distance)
1196
    {
1197 1
        $this->distance = $distance;
1198 1
    }
1199
1200
    /**
1201
     * Map a field.
1202
     *
1203
     * @param array $mapping The mapping information.
1204
     *
1205
     * @return array
1206
     *
1207
     * @throws MappingException
1208
     */
1209 953
    public function mapField(array $mapping)
1210
    {
1211 953
        if ( ! isset($mapping['fieldName']) && isset($mapping['name'])) {
1212 10
            $mapping['fieldName'] = $mapping['name'];
1213
        }
1214 953
        if ( ! isset($mapping['fieldName'])) {
1215
            throw MappingException::missingFieldName($this->name);
1216
        }
1217 953
        if ( ! isset($mapping['name'])) {
1218 943
            $mapping['name'] = $mapping['fieldName'];
1219
        }
1220 953
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1221 1
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, $mapping['name']);
1222
        }
1223 952
        if (isset($this->fieldMappings[$mapping['fieldName']])) {
1224
            //throw MappingException::duplicateFieldMapping($this->name, $mapping['fieldName']);
1225
        }
1226 952
        if ($this->discriminatorField !== null && $this->discriminatorField == $mapping['name']) {
1227 1
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1228
        }
1229 951 View Code Duplication
        if (isset($mapping['targetDocument']) && strpos($mapping['targetDocument'], '\\') === false && strlen($this->namespace)) {
1230 616
            $mapping['targetDocument'] = $this->namespace . '\\' . $mapping['targetDocument'];
1231
        }
1232 951
        if (isset($mapping['collectionClass'])) {
1233 65 View Code Duplication
            if (strpos($mapping['collectionClass'], '\\') === false && strlen($this->namespace)) {
1234 63
                $mapping['collectionClass'] = $this->namespace . '\\' . $mapping['collectionClass'];
1235
            }
1236 65
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1237
        }
1238 951
        if ( ! empty($mapping['collectionClass'])) {
1239 65
            $rColl = new \ReflectionClass($mapping['collectionClass']);
1240 65
            if ( ! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1241 1
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1242
            }
1243
        }
1244
1245 950
        if (isset($mapping['discriminatorMap'])) {
1246 123
            foreach ($mapping['discriminatorMap'] as $key => $class) {
1247 123 View Code Duplication
                if (strpos($class, '\\') === false && strlen($this->namespace)) {
1248 75
                    $mapping['discriminatorMap'][$key] = $this->namespace . '\\' . $class;
1249
                }
1250
            }
1251
        }
1252
1253 950
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1254 1
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1255
        }
1256
1257 949
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : array();
1258
1259 949
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
1260 644
            $cascades = array('remove', 'persist', 'refresh', 'merge', 'detach');
1261
        }
1262
1263 949
        if (isset($mapping['embedded'])) {
1264 601
            unset($mapping['cascade']);
1265 944
        } elseif (isset($mapping['cascade'])) {
1266 408
            $mapping['cascade'] = $cascades;
1267
        }
1268
1269 949
        $mapping['isCascadeRemove'] = in_array('remove', $cascades);
1270 949
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1271 949
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1272 949
        $mapping['isCascadeMerge'] = in_array('merge', $cascades);
1273 949
        $mapping['isCascadeDetach'] = in_array('detach', $cascades);
1274
1275 949
        if (isset($mapping['type']) && $mapping['type'] === 'file') {
1276 63
            $mapping['file'] = true;
1277
        }
1278 949
        if (isset($mapping['type']) && $mapping['type'] === 'increment') {
1279 1
            $mapping['strategy'] = self::STORAGE_STRATEGY_INCREMENT;
1280
        }
1281 949 View Code Duplication
        if (isset($mapping['file']) && $mapping['file'] === true) {
1282 63
            $this->file = $mapping['fieldName'];
1283 63
            $mapping['name'] = 'file';
1284
        }
1285 949 View Code Duplication
        if (isset($mapping['distance']) && $mapping['distance'] === true) {
1286 7
            $this->distance = $mapping['fieldName'];
1287
        }
1288 949
        if (isset($mapping['id']) && $mapping['id'] === true) {
1289 921
            $mapping['name'] = '_id';
1290 921
            $this->identifier = $mapping['fieldName'];
1291 921 View Code Duplication
            if (isset($mapping['strategy'])) {
1292 902
                $this->generatorType = constant(ClassMetadata::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
1293
            }
1294 921
            $this->generatorOptions = isset($mapping['options']) ? $mapping['options'] : array();
1295 921
            switch ($this->generatorType) {
1296 921
                case self::GENERATOR_TYPE_AUTO:
1297 847
                    $mapping['type'] = 'id';
1298 847
                    break;
1299
                default:
1300 153
                    if ( ! empty($this->generatorOptions['type'])) {
1301 52
                        $mapping['type'] = $this->generatorOptions['type'];
1302 101
                    } elseif (empty($mapping['type'])) {
1303 86
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
1304
                    }
1305
            }
1306 921
            unset($this->generatorOptions['type']);
1307
        }
1308
1309 949
        if ( ! isset($mapping['nullable'])) {
1310 53
            $mapping['nullable'] = false;
1311
        }
1312
1313
        // Synchronize the "simple" and "storeAs" mapping information for backwards compatibility
1314 949
        if (isset($mapping['simple']) && ($mapping['simple'] === true || $mapping['simple'] === 'true')) {
1315 293
            $mapping['storeAs'] = ClassMetadataInfo::REFERENCE_STORE_AS_ID;
1316 293
            @trigger_error('"simple" attribute of a reference is deprecated - use storeAs="id" instead.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1317
        }
1318
        // Provide the correct value for the "simple" field for backwards compatibility
1319 949
        if (isset($mapping['storeAs'])) {
1320 583
            $mapping['simple'] = $mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID;
1321
        }
1322
1323 949
        if (isset($mapping['reference'])
1324 596
            && isset($mapping['storeAs'])
1325 583
            && $mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID
1326 348
            && ! isset($mapping['targetDocument'])
1327
        ) {
1328 3
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
1329
        }
1330
1331 946
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
1332 112
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
1333 4
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
1334
        }
1335
1336 942
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && CollectionHelper::isAtomic($mapping['strategy'])) {
1337 1
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
1338
        }
1339
1340 941 View Code Duplication
        if (isset($mapping['reference']) && $mapping['type'] === 'one') {
1341 516
            $mapping['association'] = self::REFERENCE_ONE;
1342
        }
1343 941 View Code Duplication
        if (isset($mapping['reference']) && $mapping['type'] === 'many') {
1344 460
            $mapping['association'] = self::REFERENCE_MANY;
1345
        }
1346 941 View Code Duplication
        if (isset($mapping['embedded']) && $mapping['type'] === 'one') {
1347 462
            $mapping['association'] = self::EMBED_ONE;
1348
        }
1349 941 View Code Duplication
        if (isset($mapping['embedded']) && $mapping['type'] === 'many') {
1350 505
            $mapping['association'] = self::EMBED_MANY;
1351
        }
1352
1353 941
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
1354 133
            $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
1355
        }
1356
1357
        /*
1358
        if (isset($mapping['type']) && ($mapping['type'] === 'one' || $mapping['type'] === 'many')) {
1359
            $mapping['type'] = $mapping['type'] === 'one' ? self::ONE : self::MANY;
1360
        }
1361
        */
1362 941
        if (isset($mapping['version'])) {
1363 100
            $mapping['notSaved'] = true;
1364 100
            $this->setVersionMapping($mapping);
1365
        }
1366 941
        if (isset($mapping['lock'])) {
1367 27
            $mapping['notSaved'] = true;
1368 27
            $this->setLockMapping($mapping);
1369
        }
1370 941
        $mapping['isOwningSide'] = true;
1371 941
        $mapping['isInverseSide'] = false;
1372 941
        if (isset($mapping['reference'])) {
1373 588 View Code Duplication
            if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
1374 92
                $mapping['isOwningSide'] = true;
1375 92
                $mapping['isInverseSide'] = false;
1376
            }
1377 588 View Code Duplication
            if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
1378 295
                $mapping['isInverseSide'] = true;
1379 295
                $mapping['isOwningSide'] = false;
1380
            }
1381 588 View Code Duplication
            if (isset($mapping['repositoryMethod'])) {
1382 67
                $mapping['isInverseSide'] = true;
1383 67
                $mapping['isOwningSide'] = false;
1384
            }
1385 588
            if (!isset($mapping['orphanRemoval'])) {
1386 563
                $mapping['orphanRemoval'] = false;
1387
            }
1388
        }
1389
1390 941
        if (!empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || !$mapping['isInverseSide'])) {
1391
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
1392
        }
1393
1394 941
        $this->applyStorageStrategy($mapping);
1395
1396 940
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
1397 940
        if (isset($mapping['association'])) {
1398 741
            $this->associationMappings[$mapping['fieldName']] = $mapping;
1399
        }
1400
1401 940
        return $mapping;
1402
    }
1403
1404
    /**
1405
     * Validates the storage strategy of a mapping for consistency
1406
     * @param array $mapping
1407
     * @throws \Doctrine\ODM\MongoDB\Mapping\MappingException
1408
     */
1409 941
    private function applyStorageStrategy(array &$mapping)
1410
    {
1411 941
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1412 923
            return;
1413
        }
1414
1415
        switch (true) {
1416 903
            case $mapping['type'] == 'int':
1417 902
            case $mapping['type'] == 'float':
1418 902
            case $mapping['type'] == 'increment':
1419 334
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1420 334
                $allowedStrategies = [self::STORAGE_STRATEGY_SET, self::STORAGE_STRATEGY_INCREMENT];
1421 334
                break;
1422
1423 901
            case $mapping['type'] == 'many':
1424 618
                $defaultStrategy = CollectionHelper::DEFAULT_STRATEGY;
1425
                $allowedStrategies = [
1426 618
                    self::STORAGE_STRATEGY_PUSH_ALL,
1427 618
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1428 618
                    self::STORAGE_STRATEGY_SET,
1429 618
                    self::STORAGE_STRATEGY_SET_ARRAY,
1430 618
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1431 618
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1432
                ];
1433 618
                break;
1434
1435
            default:
1436 889
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1437 889
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1438
        }
1439
1440 903
        if (! isset($mapping['strategy'])) {
1441 892
            $mapping['strategy'] = $defaultStrategy;
1442
        }
1443
1444 903
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1445
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1446
        }
1447
1448 903
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1449 453
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1450 1
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1451
        }
1452 902
    }
1453
1454
    /**
1455
     * Map a MongoGridFSFile.
1456
     *
1457
     * @param array $mapping The mapping information.
1458
     */
1459
    public function mapFile(array $mapping)
1460
    {
1461
        $mapping['file'] = true;
1462
        $mapping['type'] = 'file';
1463
        $this->mapField($mapping);
1464
    }
1465
1466
    /**
1467
     * Map a single embedded document.
1468
     *
1469
     * @param array $mapping The mapping information.
1470
     */
1471 6
    public function mapOneEmbedded(array $mapping)
1472
    {
1473 6
        $mapping['embedded'] = true;
1474 6
        $mapping['type'] = 'one';
1475 6
        $this->mapField($mapping);
1476 5
    }
1477
1478
    /**
1479
     * Map a collection of embedded documents.
1480
     *
1481
     * @param array $mapping The mapping information.
1482
     */
1483 5
    public function mapManyEmbedded(array $mapping)
1484
    {
1485 5
        $mapping['embedded'] = true;
1486 5
        $mapping['type'] = 'many';
1487 5
        $this->mapField($mapping);
1488 5
    }
1489
1490
    /**
1491
     * Map a single document reference.
1492
     *
1493
     * @param array $mapping The mapping information.
1494
     */
1495 8
    public function mapOneReference(array $mapping)
1496
    {
1497 8
        $mapping['reference'] = true;
1498 8
        $mapping['type'] = 'one';
1499 8
        $this->mapField($mapping);
1500 8
    }
1501
1502
    /**
1503
     * Map a collection of document references.
1504
     *
1505
     * @param array $mapping The mapping information.
1506
     */
1507 8
    public function mapManyReference(array $mapping)
1508
    {
1509 8
        $mapping['reference'] = true;
1510 8
        $mapping['type'] = 'many';
1511 8
        $this->mapField($mapping);
1512 8
    }
1513
1514
    /**
1515
     * INTERNAL:
1516
     * Adds a field mapping without completing/validating it.
1517
     * This is mainly used to add inherited field mappings to derived classes.
1518
     *
1519
     * @param array $fieldMapping
1520
     */
1521 129
    public function addInheritedFieldMapping(array $fieldMapping)
1522
    {
1523 129
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1524
1525 129
        if (isset($fieldMapping['association'])) {
1526 77
            $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1527
        }
1528 129
    }
1529
1530
    /**
1531
     * INTERNAL:
1532
     * Adds an association mapping without completing/validating it.
1533
     * This is mainly used to add inherited association mappings to derived classes.
1534
     *
1535
     * @param array $mapping
1536
     *
1537
     * @return void
1538
     *
1539
     * @throws MappingException
1540
     */
1541 78
    public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
1542
    {
1543 78
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1544 78
    }
1545
1546
    /**
1547
     * Checks whether the class has a mapped association with the given field name.
1548
     *
1549
     * @param string $fieldName
1550
     * @return boolean
1551
     */
1552 14
    public function hasReference($fieldName)
1553
    {
1554 14
        return isset($this->fieldMappings[$fieldName]['reference']);
1555
    }
1556
1557
    /**
1558
     * Checks whether the class has a mapped embed with the given field name.
1559
     *
1560
     * @param string $fieldName
1561
     * @return boolean
1562
     */
1563 5
    public function hasEmbed($fieldName)
1564
    {
1565 5
        return isset($this->fieldMappings[$fieldName]['embedded']);
1566
    }
1567
1568
    /**
1569
     * {@inheritDoc}
1570
     *
1571
     * Checks whether the class has a mapped association (embed or reference) with the given field name.
1572
     */
1573 7
    public function hasAssociation($fieldName)
1574
    {
1575 7
        return $this->hasReference($fieldName) || $this->hasEmbed($fieldName);
1576
    }
1577
1578
    /**
1579
     * {@inheritDoc}
1580
     *
1581
     * Checks whether the class has a mapped reference or embed for the specified field and
1582
     * is a single valued association.
1583
     */
1584
    public function isSingleValuedAssociation($fieldName)
1585
    {
1586
        return $this->isSingleValuedReference($fieldName) || $this->isSingleValuedEmbed($fieldName);
1587
    }
1588
1589
    /**
1590
     * {@inheritDoc}
1591
     *
1592
     * Checks whether the class has a mapped reference or embed for the specified field and
1593
     * is a collection valued association.
1594
     */
1595
    public function isCollectionValuedAssociation($fieldName)
1596
    {
1597
        return $this->isCollectionValuedReference($fieldName) || $this->isCollectionValuedEmbed($fieldName);
1598
    }
1599
1600
    /**
1601
     * Checks whether the class has a mapped association for the specified field
1602
     * and if yes, checks whether it is a single-valued association (to-one).
1603
     *
1604
     * @param string $fieldName
1605
     * @return boolean TRUE if the association exists and is single-valued, FALSE otherwise.
1606
     */
1607
    public function isSingleValuedReference($fieldName)
1608
    {
1609
        return isset($this->fieldMappings[$fieldName]['association']) &&
1610
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_ONE;
1611
    }
1612
1613
    /**
1614
     * Checks whether the class has a mapped association for the specified field
1615
     * and if yes, checks whether it is a collection-valued association (to-many).
1616
     *
1617
     * @param string $fieldName
1618
     * @return boolean TRUE if the association exists and is collection-valued, FALSE otherwise.
1619
     */
1620
    public function isCollectionValuedReference($fieldName)
1621
    {
1622
        return isset($this->fieldMappings[$fieldName]['association']) &&
1623
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_MANY;
1624
    }
1625
1626
    /**
1627
     * Checks whether the class has a mapped embedded document for the specified field
1628
     * and if yes, checks whether it is a single-valued association (to-one).
1629
     *
1630
     * @param string $fieldName
1631
     * @return boolean TRUE if the association exists and is single-valued, FALSE otherwise.
1632
     */
1633
    public function isSingleValuedEmbed($fieldName)
1634
    {
1635
        return isset($this->fieldMappings[$fieldName]['association']) &&
1636
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_ONE;
1637
    }
1638
1639
    /**
1640
     * Checks whether the class has a mapped embedded document for the specified field
1641
     * and if yes, checks whether it is a collection-valued association (to-many).
1642
     *
1643
     * @param string $fieldName
1644
     * @return boolean TRUE if the association exists and is collection-valued, FALSE otherwise.
1645
     */
1646
    public function isCollectionValuedEmbed($fieldName)
1647
    {
1648
        return isset($this->fieldMappings[$fieldName]['association']) &&
1649
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_MANY;
1650
    }
1651
1652
    /**
1653
     * Sets the ID generator used to generate IDs for instances of this class.
1654
     *
1655
     * @param \Doctrine\ODM\MongoDB\Id\AbstractIdGenerator $generator
1656
     */
1657 842
    public function setIdGenerator($generator)
1658
    {
1659 842
        $this->idGenerator = $generator;
1660 842
    }
1661
1662
    /**
1663
     * Casts the identifier to its portable PHP type.
1664
     *
1665
     * @param mixed $id
1666
     * @return mixed $id
1667
     */
1668 647
    public function getPHPIdentifierValue($id)
1669
    {
1670 647
        $idType = $this->fieldMappings[$this->identifier]['type'];
1671 647
        return Type::getType($idType)->convertToPHPValue($id);
1672
    }
1673
1674
    /**
1675
     * Casts the identifier to its database type.
1676
     *
1677
     * @param mixed $id
1678
     * @return mixed $id
1679
     */
1680 714
    public function getDatabaseIdentifierValue($id)
1681
    {
1682 714
        $idType = $this->fieldMappings[$this->identifier]['type'];
1683 714
        return Type::getType($idType)->convertToDatabaseValue($id);
1684
    }
1685
1686
    /**
1687
     * Sets the document identifier of a document.
1688
     *
1689
     * The value will be converted to a PHP type before being set.
1690
     *
1691
     * @param object $document
1692
     * @param mixed $id
1693
     */
1694 576
    public function setIdentifierValue($document, $id)
1695
    {
1696 576
        $id = $this->getPHPIdentifierValue($id);
1697 576
        $this->reflFields[$this->identifier]->setValue($document, $id);
1698 576
    }
1699
1700
    /**
1701
     * Gets the document identifier as a PHP type.
1702
     *
1703
     * @param object $document
1704
     * @return mixed $id
1705
     */
1706 665
    public function getIdentifierValue($document)
1707
    {
1708 665
        return $this->reflFields[$this->identifier]->getValue($document);
1709
    }
1710
1711
    /**
1712
     * {@inheritDoc}
1713
     *
1714
     * Since MongoDB only allows exactly one identifier field this is a proxy
1715
     * to {@see getIdentifierValue()} and returns an array with the identifier
1716
     * field as a key.
1717
     */
1718
    public function getIdentifierValues($object)
1719
    {
1720
        return array($this->identifier => $this->getIdentifierValue($object));
1721
    }
1722
1723
    /**
1724
     * Get the document identifier object as a database type.
1725
     *
1726
     * @param object $document
1727
     *
1728
     * @return \MongoId $id The MongoID object.
1729
     */
1730 36
    public function getIdentifierObject($document)
1731
    {
1732 36
        return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));
1733
    }
1734
1735
    /**
1736
     * Sets the specified field to the specified value on the given document.
1737
     *
1738
     * @param object $document
1739
     * @param string $field
1740
     * @param mixed $value
1741
     */
1742 11
    public function setFieldValue($document, $field, $value)
1743
    {
1744 11
        if ($document instanceof Proxy && ! $document->__isInitialized()) {
1745
            //property changes to an uninitialized proxy will not be tracked or persisted,
1746
            //so the proxy needs to be loaded first.
1747 1
            $document->__load();
1748
        }
1749
1750 11
        $this->reflFields[$field]->setValue($document, $value);
1751 11
    }
1752
1753
    /**
1754
     * Gets the specified field's value off the given document.
1755
     *
1756
     * @param object $document
1757
     * @param string $field
1758
     *
1759
     * @return mixed
1760
     */
1761 31
    public function getFieldValue($document, $field)
1762
    {
1763 31
        if ($document instanceof Proxy && $field !== $this->identifier && ! $document->__isInitialized()) {
1764 1
            $document->__load();
1765
        }
1766
1767 31
        return $this->reflFields[$field]->getValue($document);
1768
    }
1769
1770
    /**
1771
     * Gets the mapping of a field.
1772
     *
1773
     * @param string $fieldName  The field name.
1774
     *
1775
     * @return array  The field mapping.
1776
     *
1777
     * @throws MappingException if the $fieldName is not found in the fieldMappings array
1778
     */
1779 102
    public function getFieldMapping($fieldName)
1780
    {
1781 102
        if ( ! isset($this->fieldMappings[$fieldName])) {
1782 6
            throw MappingException::mappingNotFound($this->name, $fieldName);
1783
        }
1784 100
        return $this->fieldMappings[$fieldName];
1785
    }
1786
1787
    /**
1788
     * Gets mappings of fields holding embedded document(s).
1789
     *
1790
     * @return array of field mappings
1791
     */
1792 616
    public function getEmbeddedFieldsMappings()
1793
    {
1794 616
        return array_filter(
1795 616
            $this->associationMappings,
1796
            function($assoc) { return ! empty($assoc['embedded']); }
1797
        );
1798
    }
1799
1800
    /**
1801
     * Gets the field mapping by its DB name.
1802
     * E.g. it returns identifier's mapping when called with _id.
1803
     *
1804
     * @param string $dbFieldName
1805
     *
1806
     * @return array
1807
     * @throws MappingException
1808
     */
1809 3
    public function getFieldMappingByDbFieldName($dbFieldName)
1810
    {
1811 3
        foreach ($this->fieldMappings as $mapping) {
1812 3
            if ($mapping['name'] == $dbFieldName) {
1813 3
                return $mapping;
1814
            }
1815
        }
1816
1817
        throw MappingException::mappingNotFoundByDbName($this->name, $dbFieldName);
1818
    }
1819
1820
    /**
1821
     * Check if the field is not null.
1822
     *
1823
     * @param string $fieldName  The field name
1824
     *
1825
     * @return boolean  TRUE if the field is not null, FALSE otherwise.
1826
     */
1827 1
    public function isNullable($fieldName)
1828
    {
1829 1
        $mapping = $this->getFieldMapping($fieldName);
1830 1
        if ($mapping !== false) {
1831 1
            return isset($mapping['nullable']) && $mapping['nullable'] == true;
1832
        }
1833
        return false;
1834
    }
1835
1836
    /**
1837
     * Checks whether the document has a discriminator field and value configured.
1838
     *
1839
     * @return boolean
1840
     */
1841 531
    public function hasDiscriminator()
1842
    {
1843 531
        return isset($this->discriminatorField, $this->discriminatorValue);
1844
    }
1845
1846
    /**
1847
     * Sets the type of Id generator to use for the mapped class.
1848
     *
1849
     * @param string $generatorType Generator type.
1850
     */
1851 375
    public function setIdGeneratorType($generatorType)
1852
    {
1853 375
        $this->generatorType = $generatorType;
1854 375
    }
1855
1856
    /**
1857
     * Sets the Id generator options.
1858
     *
1859
     * @param array $generatorOptions Generator options.
1860
     */
1861
    public function setIdGeneratorOptions($generatorOptions)
1862
    {
1863
        $this->generatorOptions = $generatorOptions;
1864
    }
1865
1866
    /**
1867
     * @return boolean
1868
     */
1869 622
    public function isInheritanceTypeNone()
1870
    {
1871 622
        return $this->inheritanceType == self::INHERITANCE_TYPE_NONE;
1872
    }
1873
1874
    /**
1875
     * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.
1876
     *
1877
     * @return boolean
1878
     */
1879 368
    public function isInheritanceTypeSingleCollection()
1880
    {
1881 368
        return $this->inheritanceType == self::INHERITANCE_TYPE_SINGLE_COLLECTION;
1882
    }
1883
1884
    /**
1885
     * Checks whether the mapped class uses the COLLECTION_PER_CLASS inheritance mapping strategy.
1886
     *
1887
     * @return boolean
1888
     */
1889
    public function isInheritanceTypeCollectionPerClass()
1890
    {
1891
        return $this->inheritanceType == self::INHERITANCE_TYPE_COLLECTION_PER_CLASS;
1892
    }
1893
1894
    /**
1895
     * Sets the mapped subclasses of this class.
1896
     *
1897
     * @param string[] $subclasses The names of all mapped subclasses.
1898
     */
1899 2
    public function setSubclasses(array $subclasses)
1900
    {
1901 2
        foreach ($subclasses as $subclass) {
1902 2
            if (strpos($subclass, '\\') === false && strlen($this->namespace)) {
1903 1
                $this->subClasses[] = $this->namespace . '\\' . $subclass;
1904
            } else {
1905 1
                $this->subClasses[] = $subclass;
1906
            }
1907
        }
1908 2
    }
1909
1910
    /**
1911
     * Sets the parent class names.
1912
     * Assumes that the class names in the passed array are in the order:
1913
     * directParent -> directParentParent -> directParentParentParent ... -> root.
1914
     *
1915
     * @param string[] $classNames
1916
     */
1917 900
    public function setParentClasses(array $classNames)
1918
    {
1919 900
        $this->parentClasses = $classNames;
1920
1921 900
        if (count($classNames) > 0) {
1922 113
            $this->rootDocumentName = array_pop($classNames);
1923
        }
1924 900
    }
1925
1926
    /**
1927
     * Checks whether the class will generate a new \MongoId instance for us.
1928
     *
1929
     * @return boolean TRUE if the class uses the AUTO generator, FALSE otherwise.
1930
     */
1931
    public function isIdGeneratorAuto()
1932
    {
1933
        return $this->generatorType == self::GENERATOR_TYPE_AUTO;
1934
    }
1935
1936
    /**
1937
     * Checks whether the class will use a collection to generate incremented identifiers.
1938
     *
1939
     * @return boolean TRUE if the class uses the INCREMENT generator, FALSE otherwise.
1940
     */
1941
    public function isIdGeneratorIncrement()
1942
    {
1943
        return $this->generatorType == self::GENERATOR_TYPE_INCREMENT;
1944
    }
1945
1946
    /**
1947
     * Checks whether the class will generate a uuid id.
1948
     *
1949
     * @return boolean TRUE if the class uses the UUID generator, FALSE otherwise.
1950
     */
1951
    public function isIdGeneratorUuid()
1952
    {
1953
        return $this->generatorType == self::GENERATOR_TYPE_UUID;
1954
    }
1955
1956
    /**
1957
     * Checks whether the class uses no id generator.
1958
     *
1959
     * @return boolean TRUE if the class does not use any id generator, FALSE otherwise.
1960
     */
1961
    public function isIdGeneratorNone()
1962
    {
1963
        return $this->generatorType == self::GENERATOR_TYPE_NONE;
1964
    }
1965
1966
    /**
1967
     * Sets the version field mapping used for versioning. Sets the default
1968
     * value to use depending on the column type.
1969
     *
1970
     * @param array $mapping   The version field mapping array
1971
     *
1972
     * @throws LockException
1973
     */
1974 100
    public function setVersionMapping(array &$mapping)
1975
    {
1976 100
        if ($mapping['type'] !== 'int' && $mapping['type'] !== 'date') {
1977 1
            throw LockException::invalidVersionFieldType($mapping['type']);
1978
        }
1979
1980 99
        $this->isVersioned  = true;
1981 99
        $this->versionField = $mapping['fieldName'];
1982 99
    }
1983
1984
    /**
1985
     * Sets whether this class is to be versioned for optimistic locking.
1986
     *
1987
     * @param boolean $bool
1988
     */
1989 369
    public function setVersioned($bool)
1990
    {
1991 369
        $this->isVersioned = $bool;
1992 369
    }
1993
1994
    /**
1995
     * Sets the name of the field that is to be used for versioning if this class is
1996
     * versioned for optimistic locking.
1997
     *
1998
     * @param string $versionField
1999
     */
2000 369
    public function setVersionField($versionField)
2001
    {
2002 369
        $this->versionField = $versionField;
2003 369
    }
2004
2005
    /**
2006
     * Sets the version field mapping used for versioning. Sets the default
2007
     * value to use depending on the column type.
2008
     *
2009
     * @param array $mapping   The version field mapping array
2010
     *
2011
     * @throws \Doctrine\ODM\MongoDB\LockException
2012
     */
2013 27
    public function setLockMapping(array &$mapping)
2014
    {
2015 27
        if ($mapping['type'] !== 'int') {
2016 1
            throw LockException::invalidLockFieldType($mapping['type']);
2017
        }
2018
2019 26
        $this->isLockable = true;
2020 26
        $this->lockField = $mapping['fieldName'];
2021 26
    }
2022
2023
    /**
2024
     * Sets whether this class is to allow pessimistic locking.
2025
     *
2026
     * @param boolean $bool
2027
     */
2028
    public function setLockable($bool)
2029
    {
2030
        $this->isLockable = $bool;
2031
    }
2032
2033
    /**
2034
     * Sets the name of the field that is to be used for storing whether a document
2035
     * is currently locked or not.
2036
     *
2037
     * @param string $lockField
2038
     */
2039
    public function setLockField($lockField)
2040
    {
2041
        $this->lockField = $lockField;
2042
    }
2043
2044
    /**
2045
     * {@inheritDoc}
2046
     */
2047
    public function getFieldNames()
2048
    {
2049
        return array_keys($this->fieldMappings);
2050
    }
2051
2052
    /**
2053
     * {@inheritDoc}
2054
     */
2055
    public function getAssociationNames()
2056
    {
2057
        return array_keys($this->associationMappings);
2058
    }
2059
2060
    /**
2061
     * {@inheritDoc}
2062
     */
2063 22
    public function getTypeOfField($fieldName)
2064
    {
2065 22
        return isset($this->fieldMappings[$fieldName]) ?
2066 22
            $this->fieldMappings[$fieldName]['type'] : null;
2067
    }
2068
2069
    /**
2070
     * {@inheritDoc}
2071
     */
2072 6
    public function getAssociationTargetClass($assocName)
2073
    {
2074 6
        if ( ! isset($this->associationMappings[$assocName])) {
2075 3
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
2076
        }
2077
2078 3
        return $this->associationMappings[$assocName]['targetDocument'];
2079
    }
2080
2081
    /**
2082
     * Retrieve the collectionClass associated with an association
2083
     *
2084
     * @param string $assocName
2085
     */
2086 2
    public function getAssociationCollectionClass($assocName)
2087
    {
2088 2
        if ( ! isset($this->associationMappings[$assocName])) {
2089
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
2090
        }
2091
2092 2
        if ( ! array_key_exists('collectionClass', $this->associationMappings[$assocName])) {
2093
            throw new InvalidArgumentException("collectionClass can only be applied to 'embedMany' and 'referenceMany' associations.");
2094
        }
2095
2096 2
        return $this->associationMappings[$assocName]['collectionClass'];
2097
    }
2098
2099
    /**
2100
     * {@inheritDoc}
2101
     */
2102
    public function isAssociationInverseSide($fieldName)
2103
    {
2104
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
2105
    }
2106
2107
    /**
2108
     * {@inheritDoc}
2109
     */
2110
    public function getAssociationMappedByTargetField($fieldName)
2111
    {
2112
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
2113
    }
2114
}
2115