Completed
Push — master ( 22189c...f0fe3a )
by Maciej
13s
created

ClassMetadata::setDiscriminatorMap()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5.025

Importance

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