Completed
Push — master ( 08b9e1...e0c601 )
by Andreas
13s
created

ClassMetadata::setCustomRepositoryClass()   A

Complexity

Conditions 6
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 6.105

Importance

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

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
489 1482
    }
490
491
    /**
492
     * Helper method to get reference id of ref* type references
493
     * @param mixed  $reference
494
     * @param string $storeAs
495
     * @return mixed
496
     * @internal
497
     */
498 121
    public static function getReferenceId($reference, $storeAs)
499
    {
500 121
        return $storeAs === self::REFERENCE_STORE_AS_ID ? $reference : $reference[self::getReferencePrefix($storeAs) . 'id'];
501
    }
502
503
    /**
504
     * Returns the reference prefix used for a reference
505
     * @param string $storeAs
506
     * @return string
507
     */
508 186
    private static function getReferencePrefix($storeAs)
509
    {
510 186
        if (! in_array($storeAs, [self::REFERENCE_STORE_AS_REF, self::REFERENCE_STORE_AS_DB_REF, self::REFERENCE_STORE_AS_DB_REF_WITH_DB])) {
511
            throw new \LogicException('Can only get a reference prefix for DBRef and reference arrays');
512
        }
513
514 186
        return $storeAs === self::REFERENCE_STORE_AS_REF ? '' : '$';
515
    }
516
517
    /**
518
     * Returns a fully qualified field name for a given reference
519
     * @param string $storeAs
520
     * @param string $pathPrefix The field path prefix
521
     * @return string
522
     * @internal
523
     */
524 134
    public static function getReferenceFieldName($storeAs, $pathPrefix = '')
525
    {
526 134
        if ($storeAs === self::REFERENCE_STORE_AS_ID) {
527 94
            return $pathPrefix;
528
        }
529
530 122
        return ($pathPrefix ? $pathPrefix . '.' : '') . static::getReferencePrefix($storeAs) . 'id';
0 ignored issues
show
Bug introduced by
Since getReferencePrefix() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getReferencePrefix() to at least protected.

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

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

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

}

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

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

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

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

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

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
531
    }
532
533
    /**
534
     * {@inheritDoc}
535
     */
536 1370
    public function getReflectionClass()
537
    {
538 1370
        if (! $this->reflClass) {
539
            $this->reflClass = new \ReflectionClass($this->name);
540
        }
541
542 1370
        return $this->reflClass;
543
    }
544
545
    /**
546
     * {@inheritDoc}
547
     */
548 324
    public function isIdentifier($fieldName)
549
    {
550 324
        return $this->identifier === $fieldName;
551
    }
552
553
    /**
554
     * INTERNAL:
555
     * Sets the mapped identifier field of this class.
556
     *
557
     * @param string $identifier
558
     */
559 888
    public function setIdentifier($identifier)
560
    {
561 888
        $this->identifier = $identifier;
562 888
    }
563
564
    /**
565
     * {@inheritDoc}
566
     *
567
     * Since MongoDB only allows exactly one identifier field
568
     * this will always return an array with only one value
569
     */
570 39
    public function getIdentifier()
571
    {
572 39
        return [$this->identifier];
573
    }
574
575
    /**
576
     * {@inheritDoc}
577
     *
578
     * Since MongoDB only allows exactly one identifier field
579
     * this will always return an array with only one value
580
     */
581 98
    public function getIdentifierFieldNames()
582
    {
583 98
        return [$this->identifier];
584
    }
585
586
    /**
587
     * {@inheritDoc}
588
     */
589 891
    public function hasField($fieldName)
590
    {
591 891
        return isset($this->fieldMappings[$fieldName]);
592
    }
593
594
    /**
595
     * Sets the inheritance type used by the class and it's subclasses.
596
     *
597
     * @param int $type
598
     */
599 904
    public function setInheritanceType($type)
600
    {
601 904
        $this->inheritanceType = $type;
602 904
    }
603
604
    /**
605
     * Checks whether a mapped field is inherited from an entity superclass.
606
     *
607
     * @param  string $fieldName
608
     *
609
     * @return bool TRUE if the field is inherited, FALSE otherwise.
610
     */
611 1366
    public function isInheritedField($fieldName)
612
    {
613 1366
        return isset($this->fieldMappings[$fieldName]['inherited']);
614
    }
615
616
    /**
617
     * Registers a custom repository class for the document class.
618
     *
619
     * @param string $repositoryClassName The class name of the custom repository.
620
     */
621 836
    public function setCustomRepositoryClass($repositoryClassName)
622
    {
623 836
        if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
624
            return;
625
        }
626
627 836
        if ($repositoryClassName && strpos($repositoryClassName, '\\') === false && strlen($this->namespace)) {
628 3
            $repositoryClassName = $this->namespace . '\\' . $repositoryClassName;
629
        }
630
631 836
        $this->customRepositoryClassName = $repositoryClassName;
632 836
    }
633
634
    /**
635
     * Dispatches the lifecycle event of the given document by invoking all
636
     * registered callbacks.
637
     *
638
     * @param string $event     Lifecycle event
639
     * @param object $document  Document on which the event occurred
640
     * @param array  $arguments Arguments to pass to all callbacks
641
     * @throws \InvalidArgumentException If document class is not this class or
642
     *                                   a Proxy of this class.
643
     */
644 602
    public function invokeLifecycleCallbacks($event, $document, ?array $arguments = null)
645
    {
646 602
        if (! $document instanceof $this->name) {
647 1
            throw new \InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"', $this->name, get_class($document)));
648
        }
649
650 601
        if (empty($this->lifecycleCallbacks[$event])) {
651 586
            return;
652
        }
653
654 177
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
655 177
            if ($arguments !== null) {
656 176
                call_user_func_array([$document, $callback], $arguments);
657
            } else {
658 177
                $document->$callback();
659
            }
660
        }
661 177
    }
662
663
    /**
664
     * Checks whether the class has callbacks registered for a lifecycle event.
665
     *
666
     * @param string $event Lifecycle event
667
     *
668
     * @return bool
669
     */
670
    public function hasLifecycleCallbacks($event)
671
    {
672
        return ! empty($this->lifecycleCallbacks[$event]);
673
    }
674
675
    /**
676
     * Gets the registered lifecycle callbacks for an event.
677
     *
678
     * @param string $event
679
     * @return array
680
     */
681
    public function getLifecycleCallbacks($event)
682
    {
683
        return $this->lifecycleCallbacks[$event] ?? [];
684
    }
685
686
    /**
687
     * Adds a lifecycle callback for documents of this class.
688
     *
689
     * If the callback is already registered, this is a NOOP.
690
     *
691
     * @param string $callback
692
     * @param string $event
693
     */
694 803
    public function addLifecycleCallback($callback, $event)
695
    {
696 803
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
697 1
            return;
698
        }
699
700 803
        $this->lifecycleCallbacks[$event][] = $callback;
701 803
    }
702
703
    /**
704
     * Sets the lifecycle callbacks for documents of this class.
705
     *
706
     * Any previously registered callbacks are overwritten.
707
     *
708
     * @param array $callbacks
709
     */
710 887
    public function setLifecycleCallbacks(array $callbacks)
711
    {
712 887
        $this->lifecycleCallbacks = $callbacks;
713 887
    }
714
715
    /**
716
     * Registers a method for loading document data before field hydration.
717
     *
718
     * Note: A method may be registered multiple times for different fields.
719
     * it will be invoked only once for the first field found.
720
     *
721
     * @param string       $method Method name
722
     * @param array|string $fields Database field name(s)
723
     */
724 14
    public function registerAlsoLoadMethod($method, $fields)
725
    {
726 14
        $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : [$fields];
727 14
    }
728
729
    /**
730
     * Sets the AlsoLoad methods for documents of this class.
731
     *
732
     * Any previously registered methods are overwritten.
733
     *
734
     * @param array $methods
735
     */
736 887
    public function setAlsoLoadMethods(array $methods)
737
    {
738 887
        $this->alsoLoadMethods = $methods;
739 887
    }
740
741
    /**
742
     * Sets the discriminator field.
743
     *
744
     * The field name is the the unmapped database field. Discriminator values
745
     * are only used to discern the hydration class and are not mapped to class
746
     * properties.
747
     *
748
     * @param string $discriminatorField
749
     *
750
     * @throws MappingException If the discriminator field conflicts with the
751
     *                          "name" attribute of a mapped field.
752
     */
753 913
    public function setDiscriminatorField($discriminatorField)
754
    {
755 913
        if ($discriminatorField === null) {
756 845
            $this->discriminatorField = null;
757
758 845
            return;
759
        }
760
761
        // Handle array argument with name/fieldName keys for BC
762 117
        if (is_array($discriminatorField)) {
763
            if (isset($discriminatorField['name'])) {
764
                $discriminatorField = $discriminatorField['name'];
765
            } elseif (isset($discriminatorField['fieldName'])) {
766
                $discriminatorField = $discriminatorField['fieldName'];
767
            }
768
        }
769
770 117
        foreach ($this->fieldMappings as $fieldMapping) {
771 4
            if ($discriminatorField === $fieldMapping['name']) {
772 4
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
773
            }
774
        }
775
776 116
        $this->discriminatorField = $discriminatorField;
777 116
    }
778
779
    /**
780
     * Sets the discriminator values used by this class.
781
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
782
     *
783
     * @param array $map
784
     *
785
     * @throws MappingException
786
     */
787 906
    public function setDiscriminatorMap(array $map)
788
    {
789 906
        foreach ($map as $value => $className) {
790 112
            if (strpos($className, '\\') === false && strlen($this->namespace)) {
791 82
                $className = $this->namespace . '\\' . $className;
792
            }
793 112
            $this->discriminatorMap[$value] = $className;
794 112
            if ($this->name === $className) {
795 104
                $this->discriminatorValue = $value;
796
            } else {
797 111
                if (! class_exists($className)) {
798
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
799
                }
800 111
                if (is_subclass_of($className, $this->name)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $this->name can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
801 112
                    $this->subClasses[] = $className;
802
                }
803
            }
804
        }
805 906
    }
806
807
    /**
808
     * Sets the default discriminator value to be used for this class
809
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies if the document has no discriminator value
810
     *
811
     * @param string $defaultDiscriminatorValue
812
     *
813
     * @throws MappingException
814
     */
815 890
    public function setDefaultDiscriminatorValue($defaultDiscriminatorValue)
816
    {
817 890
        if ($defaultDiscriminatorValue === null) {
818 887
            $this->defaultDiscriminatorValue = null;
819
820 887
            return;
821
        }
822
823 48
        if (! array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
824
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
825
        }
826
827 48
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
828 48
    }
829
830
    /**
831
     * Sets the discriminator value for this class.
832
     * Used for JOINED/SINGLE_TABLE inheritance and multiple document types in a single
833
     * collection.
834
     *
835
     * @param string $value
836
     */
837 3
    public function setDiscriminatorValue($value)
838
    {
839 3
        $this->discriminatorMap[$value] = $this->name;
840 3
        $this->discriminatorValue = $value;
841 3
    }
842
843
    /**
844
     * Add a index for this Document.
845
     *
846
     * @param array $keys    Array of keys for the index.
847
     * @param array $options Array of options for the index.
848
     */
849 183
    public function addIndex($keys, array $options = [])
850
    {
851 183
        $this->indexes[] = [
852
            'keys' => array_map(function ($value) {
853 183
                if ($value === 1 || $value === -1) {
854 45
                    return (int) $value;
855
                }
856 183
                if (is_string($value)) {
857 183
                    $lower = strtolower($value);
858 183
                    if ($lower === 'asc') {
859 176
                        return 1;
860
                    }
861
862 52
                    if ($lower === 'desc') {
863
                        return -1;
864
                    }
865
                }
866 52
                return $value;
867 183
            }, $keys),
868 183
            'options' => $options,
869
        ];
870 183
    }
871
872
    /**
873
     * Returns the array of indexes for this Document.
874
     *
875
     * @return array $indexes The array of indexes.
876
     */
877 23
    public function getIndexes()
878
    {
879 23
        return $this->indexes;
880
    }
881
882
    /**
883
     * Checks whether this document has indexes or not.
884
     *
885
     * @return bool
886
     */
887
    public function hasIndexes()
888
    {
889
        return $this->indexes ? true : false;
890
    }
891
892
    /**
893
     * Set shard key for this Document.
894
     *
895
     * @param array $keys    Array of document keys.
896
     * @param array $options Array of sharding options.
897
     *
898
     * @throws MappingException
899
     */
900 71
    public function setShardKey(array $keys, array $options = [])
901
    {
902 71
        if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && $this->shardKey !== null) {
903 2
            throw MappingException::shardKeyInSingleCollInheritanceSubclass($this->getName());
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
904
        }
905
906 71
        if ($this->isEmbeddedDocument) {
907 2
            throw MappingException::embeddedDocumentCantHaveShardKey($this->getName());
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
908
        }
909
910 69
        foreach (array_keys($keys) as $field) {
911 69
            if (! isset($this->fieldMappings[$field])) {
912 62
                continue;
913
            }
914
915 7
            if (in_array($this->fieldMappings[$field]['type'], ['many', 'collection'])) {
916 3
                throw MappingException::noMultiKeyShardKeys($this->getName(), $field);
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
917
            }
918
919 4
            if ($this->fieldMappings[$field]['strategy'] !== static::STORAGE_STRATEGY_SET) {
920 4
                throw MappingException::onlySetStrategyAllowedInShardKey($this->getName(), $field);
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
921
            }
922
        }
923
924 65
        $this->shardKey = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array('keys' => \array_m... 'options' => $options) of type array<string,array,{"key...ay","options":"array"}> is incompatible with the declared type string|null of property $shardKey.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
925
            'keys' => array_map(function ($value) {
926 65
                if ($value === 1 || $value === -1) {
927 5
                    return (int) $value;
928
                }
929 65
                if (is_string($value)) {
930 65
                    $lower = strtolower($value);
931 65
                    if ($lower === 'asc') {
932 63
                        return 1;
933
                    }
934
935 47
                    if ($lower === 'desc') {
936
                        return -1;
937
                    }
938
                }
939 47
                return $value;
940 65
            }, $keys),
941 65
            'options' => $options,
942
        ];
943 65
    }
944
945
    /**
946
     * @return array
947
     */
948 17
    public function getShardKey()
949
    {
950 17
        return $this->shardKey;
951
    }
952
953
    /**
954
     * Checks whether this document has shard key or not.
955
     *
956
     * @return bool
957
     */
958 1099
    public function isSharded()
959
    {
960 1099
        return $this->shardKey ? true : false;
961
    }
962
963
    /**
964
     * Sets the read preference used by this class.
965
     *
966
     * @param string     $readPreference
967
     * @param array|null $tags
968
     */
969 887
    public function setReadPreference($readPreference, $tags)
970
    {
971 887
        $this->readPreference = $readPreference;
972 887
        $this->readPreferenceTags = $tags;
973 887
    }
974
975
    /**
976
     * Sets the write concern used by this class.
977
     *
978
     * @param string $writeConcern
979
     */
980 897
    public function setWriteConcern($writeConcern)
981
    {
982 897
        $this->writeConcern = $writeConcern;
983 897
    }
984
985
    /**
986
     * @return string
987
     */
988 11
    public function getWriteConcern()
989
    {
990 11
        return $this->writeConcern;
991
    }
992
993
    /**
994
     * Whether there is a write concern configured for this class.
995
     *
996
     * @return bool
997
     */
998 551
    public function hasWriteConcern()
999
    {
1000 551
        return $this->writeConcern !== null;
1001
    }
1002
1003
    /**
1004
     * Sets the change tracking policy used by this class.
1005
     *
1006
     * @param int $policy
1007
     */
1008 889
    public function setChangeTrackingPolicy($policy)
1009
    {
1010 889
        $this->changeTrackingPolicy = $policy;
1011 889
    }
1012
1013
    /**
1014
     * Whether the change tracking policy of this class is "deferred explicit".
1015
     *
1016
     * @return bool
1017
     */
1018 62
    public function isChangeTrackingDeferredExplicit()
1019
    {
1020 62
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT;
1021
    }
1022
1023
    /**
1024
     * Whether the change tracking policy of this class is "deferred implicit".
1025
     *
1026
     * @return bool
1027
     */
1028 571
    public function isChangeTrackingDeferredImplicit()
1029
    {
1030 571
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT;
1031
    }
1032
1033
    /**
1034
     * Whether the change tracking policy of this class is "notify".
1035
     *
1036
     * @return bool
1037
     */
1038 310
    public function isChangeTrackingNotify()
1039
    {
1040 310
        return $this->changeTrackingPolicy === self::CHANGETRACKING_NOTIFY;
1041
    }
1042
1043
    /**
1044
     * Gets the ReflectionProperties of the mapped class.
1045
     *
1046
     * @return array An array of ReflectionProperty instances.
1047
     */
1048 98
    public function getReflectionProperties()
1049
    {
1050 98
        return $this->reflFields;
1051
    }
1052
1053
    /**
1054
     * Gets a ReflectionProperty for a specific field of the mapped class.
1055
     *
1056
     * @param string $name
1057
     *
1058
     * @return \ReflectionProperty
1059
     */
1060
    public function getReflectionProperty($name)
1061
    {
1062
        return $this->reflFields[$name];
1063
    }
1064
1065
    /**
1066
     * {@inheritDoc}
1067
     */
1068 1375
    public function getName()
1069
    {
1070 1375
        return $this->name;
1071
    }
1072
1073
    /**
1074
     * The namespace this Document class belongs to.
1075
     *
1076
     * @return string $namespace The namespace name.
1077
     */
1078
    public function getNamespace()
1079
    {
1080
        return $this->namespace;
1081
    }
1082
1083
    /**
1084
     * Returns the database this Document is mapped to.
1085
     *
1086
     * @return string $db The database name.
1087
     */
1088 1299
    public function getDatabase()
1089
    {
1090 1299
        return $this->db;
1091
    }
1092
1093
    /**
1094
     * Set the database this Document is mapped to.
1095
     *
1096
     * @param string $db The database name
1097
     */
1098 92
    public function setDatabase($db)
1099
    {
1100 92
        $this->db = $db;
1101 92
    }
1102
1103
    /**
1104
     * Get the collection this Document is mapped to.
1105
     *
1106
     * @return string $collection The collection name.
1107
     */
1108 1300
    public function getCollection()
1109
    {
1110 1300
        return $this->collection;
1111
    }
1112
1113
    /**
1114
     * Sets the collection this Document is mapped to.
1115
     *
1116
     * @param array|string $name
1117
     *
1118
     * @throws \InvalidArgumentException
1119
     */
1120 1482
    public function setCollection($name)
1121
    {
1122 1482
        if (is_array($name)) {
1123 1
            if (! isset($name['name'])) {
1124
                throw new \InvalidArgumentException('A name key is required when passing an array to setCollection()');
1125
            }
1126 1
            $this->collectionCapped = $name['capped'] ?? false;
1127 1
            $this->collectionSize = $name['size'] ?? 0;
1128 1
            $this->collectionMax = $name['max'] ?? 0;
1129 1
            $this->collection = $name['name'];
1130
        } else {
1131 1482
            $this->collection = $name;
1132
        }
1133 1482
    }
1134
1135
    /**
1136
     * Get whether or not the documents collection is capped.
1137
     *
1138
     * @return bool
1139
     */
1140 5
    public function getCollectionCapped()
1141
    {
1142 5
        return $this->collectionCapped;
1143
    }
1144
1145
    /**
1146
     * Set whether or not the documents collection is capped.
1147
     *
1148
     * @param bool $bool
1149
     */
1150 1
    public function setCollectionCapped($bool)
1151
    {
1152 1
        $this->collectionCapped = $bool;
1153 1
    }
1154
1155
    /**
1156
     * Get the collection size
1157
     *
1158
     * @return int
1159
     */
1160 5
    public function getCollectionSize()
1161
    {
1162 5
        return $this->collectionSize;
1163
    }
1164
1165
    /**
1166
     * Set the collection size.
1167
     *
1168
     * @param int $size
1169
     */
1170 1
    public function setCollectionSize($size)
1171
    {
1172 1
        $this->collectionSize = $size;
1173 1
    }
1174
1175
    /**
1176
     * Get the collection max.
1177
     *
1178
     * @return int
1179
     */
1180 5
    public function getCollectionMax()
1181
    {
1182 5
        return $this->collectionMax;
1183
    }
1184
1185
    /**
1186
     * Set the collection max.
1187
     *
1188
     * @param int $max
1189
     */
1190 1
    public function setCollectionMax($max)
1191
    {
1192 1
        $this->collectionMax = $max;
1193 1
    }
1194
1195
    /**
1196
     * Returns TRUE if this Document is mapped to a collection FALSE otherwise.
1197
     *
1198
     * @return bool
1199
     */
1200
    public function isMappedToCollection()
1201
    {
1202
        return $this->collection ? true : false;
1203
    }
1204
1205
    /**
1206
     * Validates the storage strategy of a mapping for consistency
1207
     * @param array $mapping
1208
     * @throws MappingException
1209
     */
1210 1392
    private function applyStorageStrategy(array &$mapping)
1211
    {
1212 1392
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1213 1374
            return;
1214
        }
1215
1216
        switch (true) {
1217 1357
            case $mapping['type'] === 'int':
1218 1356
            case $mapping['type'] === 'float':
1219 820
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1220 820
                $allowedStrategies = [self::STORAGE_STRATEGY_SET, self::STORAGE_STRATEGY_INCREMENT];
1221 820
                break;
1222
1223 1356
            case $mapping['type'] === 'many':
1224 1080
                $defaultStrategy = CollectionHelper::DEFAULT_STRATEGY;
1225
                $allowedStrategies = [
1226 1080
                    self::STORAGE_STRATEGY_PUSH_ALL,
1227 1080
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1228 1080
                    self::STORAGE_STRATEGY_SET,
1229 1080
                    self::STORAGE_STRATEGY_SET_ARRAY,
1230 1080
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1231 1080
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1232
                ];
1233 1080
                break;
1234
1235
            default:
1236 1343
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1237 1343
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1238
        }
1239
1240 1357
        if (! isset($mapping['strategy'])) {
1241 1348
            $mapping['strategy'] = $defaultStrategy;
1242
        }
1243
1244 1357
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1245
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1246
        }
1247
1248 1357
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1249 1357
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1250 1
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1251
        }
1252 1356
    }
1253
1254
    /**
1255
     * Map a single embedded document.
1256
     *
1257
     * @param array $mapping The mapping information.
1258
     */
1259 6
    public function mapOneEmbedded(array $mapping)
1260
    {
1261 6
        $mapping['embedded'] = true;
1262 6
        $mapping['type'] = 'one';
1263 6
        $this->mapField($mapping);
1264 5
    }
1265
1266
    /**
1267
     * Map a collection of embedded documents.
1268
     *
1269
     * @param array $mapping The mapping information.
1270
     */
1271 5
    public function mapManyEmbedded(array $mapping)
1272
    {
1273 5
        $mapping['embedded'] = true;
1274 5
        $mapping['type'] = 'many';
1275 5
        $this->mapField($mapping);
1276 5
    }
1277
1278
    /**
1279
     * Map a single document reference.
1280
     *
1281
     * @param array $mapping The mapping information.
1282
     */
1283 2
    public function mapOneReference(array $mapping)
1284
    {
1285 2
        $mapping['reference'] = true;
1286 2
        $mapping['type'] = 'one';
1287 2
        $this->mapField($mapping);
1288 2
    }
1289
1290
    /**
1291
     * Map a collection of document references.
1292
     *
1293
     * @param array $mapping The mapping information.
1294
     */
1295 1
    public function mapManyReference(array $mapping)
1296
    {
1297 1
        $mapping['reference'] = true;
1298 1
        $mapping['type'] = 'many';
1299 1
        $this->mapField($mapping);
1300 1
    }
1301
1302
    /**
1303
     * INTERNAL:
1304
     * Adds a field mapping without completing/validating it.
1305
     * This is mainly used to add inherited field mappings to derived classes.
1306
     *
1307
     * @param array $fieldMapping
1308
     */
1309 116
    public function addInheritedFieldMapping(array $fieldMapping)
1310
    {
1311 116
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1312
1313 116
        if (! isset($fieldMapping['association'])) {
1314 116
            return;
1315
        }
1316
1317 67
        $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1318 67
    }
1319
1320
    /**
1321
     * INTERNAL:
1322
     * Adds an association mapping without completing/validating it.
1323
     * This is mainly used to add inherited association mappings to derived classes.
1324
     *
1325
     * @param array $mapping
1326
     *
1327
     *
1328
     * @throws MappingException
1329
     */
1330 68
    public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
1331
    {
1332 68
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1333 68
    }
1334
1335
    /**
1336
     * Checks whether the class has a mapped association with the given field name.
1337
     *
1338
     * @param string $fieldName
1339
     * @return bool
1340
     */
1341 32
    public function hasReference($fieldName)
1342
    {
1343 32
        return isset($this->fieldMappings[$fieldName]['reference']);
1344
    }
1345
1346
    /**
1347
     * Checks whether the class has a mapped embed with the given field name.
1348
     *
1349
     * @param string $fieldName
1350
     * @return bool
1351
     */
1352 5
    public function hasEmbed($fieldName)
1353
    {
1354 5
        return isset($this->fieldMappings[$fieldName]['embedded']);
1355
    }
1356
1357
    /**
1358
     * {@inheritDoc}
1359
     *
1360
     * Checks whether the class has a mapped association (embed or reference) with the given field name.
1361
     */
1362 7
    public function hasAssociation($fieldName)
1363
    {
1364 7
        return $this->hasReference($fieldName) || $this->hasEmbed($fieldName);
1365
    }
1366
1367
    /**
1368
     * {@inheritDoc}
1369
     *
1370
     * Checks whether the class has a mapped reference or embed for the specified field and
1371
     * is a single valued association.
1372
     */
1373
    public function isSingleValuedAssociation($fieldName)
1374
    {
1375
        return $this->isSingleValuedReference($fieldName) || $this->isSingleValuedEmbed($fieldName);
1376
    }
1377
1378
    /**
1379
     * {@inheritDoc}
1380
     *
1381
     * Checks whether the class has a mapped reference or embed for the specified field and
1382
     * is a collection valued association.
1383
     */
1384
    public function isCollectionValuedAssociation($fieldName)
1385
    {
1386
        return $this->isCollectionValuedReference($fieldName) || $this->isCollectionValuedEmbed($fieldName);
1387
    }
1388
1389
    /**
1390
     * Checks whether the class has a mapped association for the specified field
1391
     * and if yes, checks whether it is a single-valued association (to-one).
1392
     *
1393
     * @param string $fieldName
1394
     * @return bool TRUE if the association exists and is single-valued, FALSE otherwise.
1395
     */
1396
    public function isSingleValuedReference($fieldName)
1397
    {
1398
        return isset($this->fieldMappings[$fieldName]['association']) &&
1399
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_ONE;
1400
    }
1401
1402
    /**
1403
     * Checks whether the class has a mapped association for the specified field
1404
     * and if yes, checks whether it is a collection-valued association (to-many).
1405
     *
1406
     * @param string $fieldName
1407
     * @return bool TRUE if the association exists and is collection-valued, FALSE otherwise.
1408
     */
1409
    public function isCollectionValuedReference($fieldName)
1410
    {
1411
        return isset($this->fieldMappings[$fieldName]['association']) &&
1412
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_MANY;
1413
    }
1414
1415
    /**
1416
     * Checks whether the class has a mapped embedded document for the specified field
1417
     * and if yes, checks whether it is a single-valued association (to-one).
1418
     *
1419
     * @param string $fieldName
1420
     * @return bool TRUE if the association exists and is single-valued, FALSE otherwise.
1421
     */
1422
    public function isSingleValuedEmbed($fieldName)
1423
    {
1424
        return isset($this->fieldMappings[$fieldName]['association']) &&
1425
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_ONE;
1426
    }
1427
1428
    /**
1429
     * Checks whether the class has a mapped embedded document for the specified field
1430
     * and if yes, checks whether it is a collection-valued association (to-many).
1431
     *
1432
     * @param string $fieldName
1433
     * @return bool TRUE if the association exists and is collection-valued, FALSE otherwise.
1434
     */
1435
    public function isCollectionValuedEmbed($fieldName)
1436
    {
1437
        return isset($this->fieldMappings[$fieldName]['association']) &&
1438
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_MANY;
1439
    }
1440
1441
    /**
1442
     * Sets the ID generator used to generate IDs for instances of this class.
1443
     *
1444
     * @param AbstractIdGenerator $generator
1445
     */
1446 1311
    public function setIdGenerator($generator)
1447
    {
1448 1311
        $this->idGenerator = $generator;
1449 1311
    }
1450
1451
    /**
1452
     * Casts the identifier to its portable PHP type.
1453
     *
1454
     * @param mixed $id
1455
     * @return mixed $id
1456
     */
1457 594
    public function getPHPIdentifierValue($id)
1458
    {
1459 594
        $idType = $this->fieldMappings[$this->identifier]['type'];
1460 594
        return Type::getType($idType)->convertToPHPValue($id);
1461
    }
1462
1463
    /**
1464
     * Casts the identifier to its database type.
1465
     *
1466
     * @param mixed $id
1467
     * @return mixed $id
1468
     */
1469 657
    public function getDatabaseIdentifierValue($id)
1470
    {
1471 657
        $idType = $this->fieldMappings[$this->identifier]['type'];
1472 657
        return Type::getType($idType)->convertToDatabaseValue($id);
1473
    }
1474
1475
    /**
1476
     * Sets the document identifier of a document.
1477
     *
1478
     * The value will be converted to a PHP type before being set.
1479
     *
1480
     * @param object $document
1481
     * @param mixed  $id
1482
     */
1483 523
    public function setIdentifierValue($document, $id)
1484
    {
1485 523
        $id = $this->getPHPIdentifierValue($id);
1486 523
        $this->reflFields[$this->identifier]->setValue($document, $id);
1487 523
    }
1488
1489
    /**
1490
     * Gets the document identifier as a PHP type.
1491
     *
1492
     * @param object $document
1493
     * @return mixed $id
1494
     */
1495 606
    public function getIdentifierValue($document)
1496
    {
1497 606
        return $this->reflFields[$this->identifier]->getValue($document);
1498
    }
1499
1500
    /**
1501
     * {@inheritDoc}
1502
     *
1503
     * Since MongoDB only allows exactly one identifier field this is a proxy
1504
     * to {@see getIdentifierValue()} and returns an array with the identifier
1505
     * field as a key.
1506
     */
1507
    public function getIdentifierValues($object)
1508
    {
1509
        return [$this->identifier => $this->getIdentifierValue($object)];
1510
    }
1511
1512
    /**
1513
     * Get the document identifier object as a database type.
1514
     *
1515
     * @param object $document
1516
     *
1517
     * @return ObjectId $id The ObjectId
1518
     */
1519 31
    public function getIdentifierObject($document)
1520
    {
1521 31
        return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));
1522
    }
1523
1524
    /**
1525
     * Sets the specified field to the specified value on the given document.
1526
     *
1527
     * @param object $document
1528
     * @param string $field
1529
     * @param mixed  $value
1530
     */
1531 8
    public function setFieldValue($document, $field, $value)
1532
    {
1533 8
        if ($document instanceof Proxy && ! $document->__isInitialized()) {
1534
            //property changes to an uninitialized proxy will not be tracked or persisted,
1535
            //so the proxy needs to be loaded first.
1536 1
            $document->__load();
1537
        }
1538
1539 8
        $this->reflFields[$field]->setValue($document, $value);
1540 8
    }
1541
1542
    /**
1543
     * Gets the specified field's value off the given document.
1544
     *
1545
     * @param object $document
1546
     * @param string $field
1547
     *
1548
     * @return mixed
1549
     */
1550 27
    public function getFieldValue($document, $field)
1551
    {
1552 27
        if ($document instanceof Proxy && $field !== $this->identifier && ! $document->__isInitialized()) {
1553 1
            $document->__load();
1554
        }
1555
1556 27
        return $this->reflFields[$field]->getValue($document);
1557
    }
1558
1559
    /**
1560
     * Gets the mapping of a field.
1561
     *
1562
     * @param string $fieldName The field name.
1563
     *
1564
     * @return array  The field mapping.
1565
     *
1566
     * @throws MappingException If the $fieldName is not found in the fieldMappings array.
1567
     */
1568 169
    public function getFieldMapping($fieldName)
1569
    {
1570 169
        if (! isset($this->fieldMappings[$fieldName])) {
1571 6
            throw MappingException::mappingNotFound($this->name, $fieldName);
1572
        }
1573 167
        return $this->fieldMappings[$fieldName];
1574
    }
1575
1576
    /**
1577
     * Gets mappings of fields holding embedded document(s).
1578
     *
1579
     * @return array of field mappings
1580
     */
1581 562
    public function getEmbeddedFieldsMappings()
1582
    {
1583 562
        return array_filter(
1584 562
            $this->associationMappings,
1585
            function ($assoc) {
1586 427
                return ! empty($assoc['embedded']);
1587 562
            }
1588
        );
1589
    }
1590
1591
    /**
1592
     * Gets the field mapping by its DB name.
1593
     * E.g. it returns identifier's mapping when called with _id.
1594
     *
1595
     * @param string $dbFieldName
1596
     *
1597
     * @return array
1598
     * @throws MappingException
1599
     */
1600 4
    public function getFieldMappingByDbFieldName($dbFieldName)
1601
    {
1602 4
        foreach ($this->fieldMappings as $mapping) {
1603 4
            if ($mapping['name'] === $dbFieldName) {
1604 4
                return $mapping;
1605
            }
1606
        }
1607
1608
        throw MappingException::mappingNotFoundByDbName($this->name, $dbFieldName);
1609
    }
1610
1611
    /**
1612
     * Check if the field is not null.
1613
     *
1614
     * @param string $fieldName The field name
1615
     *
1616
     * @return bool  TRUE if the field is not null, FALSE otherwise.
1617
     */
1618 1
    public function isNullable($fieldName)
1619
    {
1620 1
        $mapping = $this->getFieldMapping($fieldName);
1621 1
        if ($mapping !== false) {
1622 1
            return isset($mapping['nullable']) && $mapping['nullable'] === true;
1623
        }
1624
        return false;
1625
    }
1626
1627
    /**
1628
     * Checks whether the document has a discriminator field and value configured.
1629
     *
1630
     * @return bool
1631
     */
1632 497
    public function hasDiscriminator()
1633
    {
1634 497
        return isset($this->discriminatorField, $this->discriminatorValue);
1635
    }
1636
1637
    /**
1638
     * Sets the type of Id generator to use for the mapped class.
1639
     *
1640
     * @param string $generatorType Generator type.
1641
     */
1642 887
    public function setIdGeneratorType($generatorType)
1643
    {
1644 887
        $this->generatorType = $generatorType;
1645 887
    }
1646
1647
    /**
1648
     * Sets the Id generator options.
1649
     *
1650
     * @param array $generatorOptions Generator options.
1651
     */
1652
    public function setIdGeneratorOptions($generatorOptions)
1653
    {
1654
        $this->generatorOptions = $generatorOptions;
1655
    }
1656
1657
    /**
1658
     * @return bool
1659
     */
1660 569
    public function isInheritanceTypeNone()
1661
    {
1662 569
        return $this->inheritanceType === self::INHERITANCE_TYPE_NONE;
1663
    }
1664
1665
    /**
1666
     * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.
1667
     *
1668
     * @return bool
1669
     */
1670 886
    public function isInheritanceTypeSingleCollection()
1671
    {
1672 886
        return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION;
1673
    }
1674
1675
    /**
1676
     * Checks whether the mapped class uses the COLLECTION_PER_CLASS inheritance mapping strategy.
1677
     *
1678
     * @return bool
1679
     */
1680
    public function isInheritanceTypeCollectionPerClass()
1681
    {
1682
        return $this->inheritanceType === self::INHERITANCE_TYPE_COLLECTION_PER_CLASS;
1683
    }
1684
1685
    /**
1686
     * Sets the mapped subclasses of this class.
1687
     *
1688
     * @param string[] $subclasses The names of all mapped subclasses.
1689
     */
1690 2
    public function setSubclasses(array $subclasses)
1691
    {
1692 2
        foreach ($subclasses as $subclass) {
1693 2
            if (strpos($subclass, '\\') === false && strlen($this->namespace)) {
1694 1
                $this->subClasses[] = $this->namespace . '\\' . $subclass;
1695
            } else {
1696 2
                $this->subClasses[] = $subclass;
1697
            }
1698
        }
1699 2
    }
1700
1701
    /**
1702
     * Sets the parent class names.
1703
     * Assumes that the class names in the passed array are in the order:
1704
     * directParent -> directParentParent -> directParentParentParent ... -> root.
1705
     *
1706
     * @param string[] $classNames
1707
     */
1708 1366
    public function setParentClasses(array $classNames)
1709
    {
1710 1366
        $this->parentClasses = $classNames;
1711
1712 1366
        if (count($classNames) <= 0) {
1713 1365
            return;
1714
        }
1715
1716 100
        $this->rootDocumentName = array_pop($classNames);
1717 100
    }
1718
1719
    /**
1720
     * Checks whether the class will generate a new \MongoDB\BSON\ObjectId instance for us.
1721
     *
1722
     * @return bool TRUE if the class uses the AUTO generator, FALSE otherwise.
1723
     */
1724
    public function isIdGeneratorAuto()
1725
    {
1726
        return $this->generatorType === self::GENERATOR_TYPE_AUTO;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->generatorType (string) and self::GENERATOR_TYPE_AUTO (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1727
    }
1728
1729
    /**
1730
     * Checks whether the class will use a collection to generate incremented identifiers.
1731
     *
1732
     * @return bool TRUE if the class uses the INCREMENT generator, FALSE otherwise.
1733
     */
1734
    public function isIdGeneratorIncrement()
1735
    {
1736
        return $this->generatorType === self::GENERATOR_TYPE_INCREMENT;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->generatorType (string) and self::GENERATOR_TYPE_INCREMENT (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1737
    }
1738
1739
    /**
1740
     * Checks whether the class will generate a uuid id.
1741
     *
1742
     * @return bool TRUE if the class uses the UUID generator, FALSE otherwise.
1743
     */
1744
    public function isIdGeneratorUuid()
1745
    {
1746
        return $this->generatorType === self::GENERATOR_TYPE_UUID;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->generatorType (string) and self::GENERATOR_TYPE_UUID (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1747
    }
1748
1749
    /**
1750
     * Checks whether the class uses no id generator.
1751
     *
1752
     * @return bool TRUE if the class does not use any id generator, FALSE otherwise.
1753
     */
1754
    public function isIdGeneratorNone()
1755
    {
1756
        return $this->generatorType === self::GENERATOR_TYPE_NONE;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->generatorType (string) and self::GENERATOR_TYPE_NONE (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1757
    }
1758
1759
    /**
1760
     * Sets the version field mapping used for versioning. Sets the default
1761
     * value to use depending on the column type.
1762
     *
1763
     * @param array $mapping The version field mapping array
1764
     *
1765
     * @throws LockException
1766
     */
1767 67
    public function setVersionMapping(array &$mapping)
1768
    {
1769 67
        if ($mapping['type'] !== 'int' && $mapping['type'] !== 'date') {
1770 1
            throw LockException::invalidVersionFieldType($mapping['type']);
1771
        }
1772
1773 66
        $this->isVersioned  = true;
1774 66
        $this->versionField = $mapping['fieldName'];
1775 66
    }
1776
1777
    /**
1778
     * Sets whether this class is to be versioned for optimistic locking.
1779
     *
1780
     * @param bool $bool
1781
     */
1782 887
    public function setVersioned($bool)
1783
    {
1784 887
        $this->isVersioned = $bool;
1785 887
    }
1786
1787
    /**
1788
     * Sets the name of the field that is to be used for versioning if this class is
1789
     * versioned for optimistic locking.
1790
     *
1791
     * @param string $versionField
1792
     */
1793 887
    public function setVersionField($versionField)
1794
    {
1795 887
        $this->versionField = $versionField;
1796 887
    }
1797
1798
    /**
1799
     * Sets the version field mapping used for versioning. Sets the default
1800
     * value to use depending on the column type.
1801
     *
1802
     * @param array $mapping The version field mapping array
1803
     *
1804
     * @throws LockException
1805
     */
1806 22
    public function setLockMapping(array &$mapping)
1807
    {
1808 22
        if ($mapping['type'] !== 'int') {
1809 1
            throw LockException::invalidLockFieldType($mapping['type']);
1810
        }
1811
1812 21
        $this->isLockable = true;
1813 21
        $this->lockField = $mapping['fieldName'];
1814 21
    }
1815
1816
    /**
1817
     * Sets whether this class is to allow pessimistic locking.
1818
     *
1819
     * @param bool $bool
1820
     */
1821
    public function setLockable($bool)
1822
    {
1823
        $this->isLockable = $bool;
1824
    }
1825
1826
    /**
1827
     * Sets the name of the field that is to be used for storing whether a document
1828
     * is currently locked or not.
1829
     *
1830
     * @param string $lockField
1831
     */
1832
    public function setLockField($lockField)
1833
    {
1834
        $this->lockField = $lockField;
1835
    }
1836
1837
    /**
1838
     * Marks this class as read only, no change tracking is applied to it.
1839
     */
1840 5
    public function markReadOnly()
1841
    {
1842 5
        $this->isReadOnly = true;
1843 5
    }
1844
1845
    /**
1846
     * {@inheritDoc}
1847
     */
1848
    public function getFieldNames()
1849
    {
1850
        return array_keys($this->fieldMappings);
1851
    }
1852
1853
    /**
1854
     * {@inheritDoc}
1855
     */
1856
    public function getAssociationNames()
1857
    {
1858
        return array_keys($this->associationMappings);
1859
    }
1860
1861
    /**
1862
     * {@inheritDoc}
1863
     */
1864 23
    public function getTypeOfField($fieldName)
1865
    {
1866 23
        return isset($this->fieldMappings[$fieldName]) ?
1867 23
            $this->fieldMappings[$fieldName]['type'] : null;
1868
    }
1869
1870
    /**
1871
     * {@inheritDoc}
1872
     */
1873 4
    public function getAssociationTargetClass($assocName)
1874
    {
1875 4
        if (! isset($this->associationMappings[$assocName])) {
1876 2
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1877
        }
1878
1879 2
        return $this->associationMappings[$assocName]['targetDocument'];
1880
    }
1881
1882
    /**
1883
     * Retrieve the collectionClass associated with an association
1884
     *
1885
     * @param string $assocName
1886
     */
1887 1
    public function getAssociationCollectionClass($assocName)
1888
    {
1889 1
        if (! isset($this->associationMappings[$assocName])) {
1890
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1891
        }
1892
1893 1
        if (! array_key_exists('collectionClass', $this->associationMappings[$assocName])) {
1894
            throw new InvalidArgumentException("collectionClass can only be applied to 'embedMany' and 'referenceMany' associations.");
1895
        }
1896
1897 1
        return $this->associationMappings[$assocName]['collectionClass'];
1898
    }
1899
1900
    /**
1901
     * {@inheritDoc}
1902
     */
1903
    public function isAssociationInverseSide($fieldName)
1904
    {
1905
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1906
    }
1907
1908
    /**
1909
     * {@inheritDoc}
1910
     */
1911
    public function getAssociationMappedByTargetField($fieldName)
1912
    {
1913
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1914
    }
1915
1916
    /**
1917
     * Map a field.
1918
     *
1919
     * @param array $mapping The mapping information.
1920
     *
1921
     * @return array
1922
     *
1923
     * @throws MappingException
1924
     */
1925 1407
    public function mapField(array $mapping)
1926
    {
1927 1407
        if (! isset($mapping['fieldName']) && isset($mapping['name'])) {
1928 5
            $mapping['fieldName'] = $mapping['name'];
1929
        }
1930 1407
        if (! isset($mapping['fieldName'])) {
1931
            throw MappingException::missingFieldName($this->name);
1932
        }
1933 1407
        if (! isset($mapping['name'])) {
1934 1405
            $mapping['name'] = $mapping['fieldName'];
1935
        }
1936 1407
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1937 1
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, $mapping['name']);
1938
        }
1939 1406
        if ($this->discriminatorField !== null && $this->discriminatorField === $mapping['name']) {
1940 1
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1941
        }
1942 1405
        if (isset($mapping['targetDocument']) && strpos($mapping['targetDocument'], '\\') === false && strlen($this->namespace)) {
1943 1107
            $mapping['targetDocument'] = $this->namespace . '\\' . $mapping['targetDocument'];
1944
        }
1945 1405
        if (isset($mapping['collectionClass'])) {
1946 53
            if (strpos($mapping['collectionClass'], '\\') === false && strlen($this->namespace)) {
1947 51
                $mapping['collectionClass'] = $this->namespace . '\\' . $mapping['collectionClass'];
1948
            }
1949 53
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1950
        }
1951 1405
        if (! empty($mapping['collectionClass'])) {
1952 53
            $rColl = new \ReflectionClass($mapping['collectionClass']);
1953 53
            if (! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1954 1
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1955
            }
1956
        }
1957
1958 1404
        if (isset($mapping['discriminatorMap'])) {
1959 114
            foreach ($mapping['discriminatorMap'] as $key => $class) {
1960 114
                if (strpos($class, '\\') !== false || ! strlen($this->namespace)) {
1961 94
                    continue;
1962
                }
1963
1964 63
                $mapping['discriminatorMap'][$key] = $this->namespace . '\\' . $class;
1965
            }
1966
        }
1967
1968 1404
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1969 1
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1970
        }
1971
1972 1403
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : [];
1973
1974 1403
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
1975 1107
            $cascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1976
        }
1977
1978 1403
        if (isset($mapping['embedded'])) {
1979 1067
            unset($mapping['cascade']);
1980 1398
        } elseif (isset($mapping['cascade'])) {
1981 904
            $mapping['cascade'] = $cascades;
1982
        }
1983
1984 1403
        $mapping['isCascadeRemove'] = in_array('remove', $cascades);
1985 1403
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1986 1403
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1987 1403
        $mapping['isCascadeMerge'] = in_array('merge', $cascades);
1988 1403
        $mapping['isCascadeDetach'] = in_array('detach', $cascades);
1989
1990 1403
        if (isset($mapping['id']) && $mapping['id'] === true) {
1991 1372
            $mapping['name'] = '_id';
1992 1372
            $this->identifier = $mapping['fieldName'];
1993 1372
            if (isset($mapping['strategy'])) {
1994 1366
                $this->generatorType = constant(self::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
1995
            }
1996 1372
            $this->generatorOptions = $mapping['options'] ?? [];
1997 1372
            switch ($this->generatorType) {
1998 1372
                case self::GENERATOR_TYPE_AUTO:
1999 1300
                    $mapping['type'] = 'id';
2000 1300
                    break;
2001
                default:
2002 145
                    if (! empty($this->generatorOptions['type'])) {
2003 56
                        $mapping['type'] = $this->generatorOptions['type'];
2004 89
                    } elseif (empty($mapping['type'])) {
2005 77
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
2006
                    }
2007
            }
2008 1372
            unset($this->generatorOptions['type']);
2009
        }
2010
2011 1403
        if (! isset($mapping['nullable'])) {
2012 38
            $mapping['nullable'] = false;
2013
        }
2014
2015 1403
        if (isset($mapping['reference'])
2016 1403
            && isset($mapping['storeAs'])
2017 1403
            && $mapping['storeAs'] === self::REFERENCE_STORE_AS_ID
2018 1403
            && ! isset($mapping['targetDocument'])
2019
        ) {
2020 3
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
2021
        }
2022
2023 1400
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
2024 1400
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
2025 4
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
2026
        }
2027
2028 1396
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
2029 1
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
2030
        }
2031
2032 1395
        if (isset($mapping['repositoryMethod']) && ! (empty($mapping['skip']) && empty($mapping['limit']) && empty($mapping['sort']))) {
2033 3
            throw MappingException::repositoryMethodCanNotBeCombinedWithSkipLimitAndSort($this->name, $mapping['fieldName']);
2034
        }
2035
2036 1392
        if (isset($mapping['reference']) && $mapping['type'] === 'one') {
2037 1000
            $mapping['association'] = self::REFERENCE_ONE;
2038
        }
2039 1392
        if (isset($mapping['reference']) && $mapping['type'] === 'many') {
2040 955
            $mapping['association'] = self::REFERENCE_MANY;
2041
        }
2042 1392
        if (isset($mapping['embedded']) && $mapping['type'] === 'one') {
2043 944
            $mapping['association'] = self::EMBED_ONE;
2044
        }
2045 1392
        if (isset($mapping['embedded']) && $mapping['type'] === 'many') {
2046 978
            $mapping['association'] = self::EMBED_MANY;
2047
        }
2048
2049 1392
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
2050 124
            $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
2051
        }
2052
2053
        /*
2054
        if (isset($mapping['type']) && ($mapping['type'] === 'one' || $mapping['type'] === 'many')) {
2055
            $mapping['type'] = $mapping['type'] === 'one' ? self::ONE : self::MANY;
2056
        }
2057
        */
2058 1392
        if (isset($mapping['version'])) {
2059 67
            $mapping['notSaved'] = true;
2060 67
            $this->setVersionMapping($mapping);
2061
        }
2062 1392
        if (isset($mapping['lock'])) {
2063 22
            $mapping['notSaved'] = true;
2064 22
            $this->setLockMapping($mapping);
2065
        }
2066 1392
        $mapping['isOwningSide'] = true;
2067 1392
        $mapping['isInverseSide'] = false;
2068 1392
        if (isset($mapping['reference'])) {
2069 1065
            if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
2070 84
                $mapping['isOwningSide'] = true;
2071 84
                $mapping['isInverseSide'] = false;
2072
            }
2073 1065
            if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
2074 814
                $mapping['isInverseSide'] = true;
2075 814
                $mapping['isOwningSide'] = false;
2076
            }
2077 1065
            if (isset($mapping['repositoryMethod'])) {
2078 58
                $mapping['isInverseSide'] = true;
2079 58
                $mapping['isOwningSide'] = false;
2080
            }
2081 1065
            if (! isset($mapping['orphanRemoval'])) {
2082 1045
                $mapping['orphanRemoval'] = false;
2083
            }
2084
        }
2085
2086 1392
        if (! empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || ! $mapping['isInverseSide'])) {
2087
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
2088
        }
2089
2090 1392
        $this->applyStorageStrategy($mapping);
2091
2092 1391
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
2093 1391
        if (isset($mapping['association'])) {
2094 1199
            $this->associationMappings[$mapping['fieldName']] = $mapping;
2095
        }
2096
2097 1391
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
2098 1390
        $reflProp->setAccessible(true);
2099 1390
        $this->reflFields[$mapping['fieldName']] = $reflProp;
2100
2101 1390
        return $mapping;
2102
    }
2103
2104
    /**
2105
     * Determines which fields get serialized.
2106
     *
2107
     * It is only serialized what is necessary for best unserialization performance.
2108
     * That means any metadata properties that are not set or empty or simply have
2109
     * their default value are NOT serialized.
2110
     *
2111
     * Parts that are also NOT serialized because they can not be properly unserialized:
2112
     *      - reflClass (ReflectionClass)
2113
     *      - reflFields (ReflectionProperty array)
2114
     *
2115
     * @return array The names of all the fields that should be serialized.
2116
     */
2117 6
    public function __sleep()
2118
    {
2119
        // This metadata is always serialized/cached.
2120
        $serialized = [
2121 6
            'fieldMappings',
2122
            'associationMappings',
2123
            'identifier',
2124
            'name',
2125
            'namespace', // TODO: REMOVE
2126
            'db',
2127
            'collection',
2128
            'readPreference',
2129
            'readPreferenceTags',
2130
            'writeConcern',
2131
            'rootDocumentName',
2132
            'generatorType',
2133
            'generatorOptions',
2134
            'idGenerator',
2135
            'indexes',
2136
            'shardKey',
2137
        ];
2138
2139
        // The rest of the metadata is only serialized if necessary.
2140 6
        if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {
2141
            $serialized[] = 'changeTrackingPolicy';
2142
        }
2143
2144 6
        if ($this->customRepositoryClassName) {
2145 1
            $serialized[] = 'customRepositoryClassName';
2146
        }
2147
2148 6
        if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE || $this->discriminatorField !== null) {
2149 4
            $serialized[] = 'inheritanceType';
2150 4
            $serialized[] = 'discriminatorField';
2151 4
            $serialized[] = 'discriminatorValue';
2152 4
            $serialized[] = 'discriminatorMap';
2153 4
            $serialized[] = 'defaultDiscriminatorValue';
2154 4
            $serialized[] = 'parentClasses';
2155 4
            $serialized[] = 'subClasses';
2156
        }
2157
2158 6
        if ($this->isMappedSuperclass) {
2159 1
            $serialized[] = 'isMappedSuperclass';
2160
        }
2161
2162 6
        if ($this->isEmbeddedDocument) {
2163 1
            $serialized[] = 'isEmbeddedDocument';
2164
        }
2165
2166 6
        if ($this->isQueryResultDocument) {
2167
            $serialized[] = 'isQueryResultDocument';
2168
        }
2169
2170 6
        if ($this->isVersioned) {
2171
            $serialized[] = 'isVersioned';
2172
            $serialized[] = 'versionField';
2173
        }
2174
2175 6
        if ($this->lifecycleCallbacks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->lifecycleCallbacks of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
2176
            $serialized[] = 'lifecycleCallbacks';
2177
        }
2178
2179 6
        if ($this->collectionCapped) {
2180 1
            $serialized[] = 'collectionCapped';
2181 1
            $serialized[] = 'collectionSize';
2182 1
            $serialized[] = 'collectionMax';
2183
        }
2184
2185 6
        if ($this->isReadOnly) {
2186
            $serialized[] = 'isReadOnly';
2187
        }
2188
2189 6
        return $serialized;
2190
    }
2191
2192
    /**
2193
     * Restores some state that can not be serialized/unserialized.
2194
     *
2195
     */
2196 6
    public function __wakeup()
2197
    {
2198
        // Restore ReflectionClass and properties
2199 6
        $this->reflClass = new \ReflectionClass($this->name);
2200 6
        $this->instantiator = $this->instantiator ?: new Instantiator();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->instantiator ?: n...antiator\Instantiator() can also be of type object<Doctrine\Instantiator\Instantiator>. However, the property $instantiator is declared as type object<Doctrine\Instanti...antiatorInterface>|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2201
2202 6
        foreach ($this->fieldMappings as $field => $mapping) {
2203 3
            if (isset($mapping['declared'])) {
2204 1
                $reflField = new \ReflectionProperty($mapping['declared'], $field);
2205
            } else {
2206 3
                $reflField = $this->reflClass->getProperty($field);
2207
            }
2208 3
            $reflField->setAccessible(true);
2209 3
            $this->reflFields[$field] = $reflField;
2210
        }
2211 6
    }
2212
2213
    /**
2214
     * Creates a new instance of the mapped class, without invoking the constructor.
2215
     *
2216
     * @return object
2217
     */
2218 355
    public function newInstance()
2219
    {
2220 355
        return $this->instantiator->instantiate($this->name);
2221
    }
2222
}
2223