Completed
Pull Request — master (#1812)
by Catalin
15:03
created

ClassMetadata::invokeLifecycleCallbacks()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 10
cts 10
cp 1
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 10
nc 5
nop 3
crap 5
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 1482
    public function __construct($documentName)
472
    {
473 1482
        $this->name = $documentName;
474 1482
        $this->rootDocumentName = $documentName;
475 1482
        $this->reflClass = new \ReflectionClass($documentName);
476 1482
        $this->setCollection($this->reflClass->getShortName());
477 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...
478 1482
    }
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 1370
    public function getReflectionClass()
526
    {
527 1370
        if (! $this->reflClass) {
528
            $this->reflClass = new \ReflectionClass($this->name);
529
        }
530
531 1370
        return $this->reflClass;
532
    }
533
534
    /**
535
     * {@inheritDoc}
536
     */
537 324
    public function isIdentifier($fieldName)
538
    {
539 324
        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 888
    public function setIdentifier($identifier)
549
    {
550 888
        $this->identifier = $identifier;
551 888
    }
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 891
    public function hasField($fieldName)
579
    {
580 891
        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 904
    public function setInheritanceType($type)
589
    {
590 904
        $this->inheritanceType = $type;
591 904
    }
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 1366
    public function isInheritedField($fieldName)
601
    {
602 1366
        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 836
    public function setCustomRepositoryClass($repositoryClassName)
611
    {
612 836
        if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
613
            return;
614
        }
615
616 836
        $this->customRepositoryClassName = $repositoryClassName;
617 836
    }
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 602
    public function invokeLifecycleCallbacks($event, $document, ?array $arguments = null)
630
    {
631 602
        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 601
        if (empty($this->lifecycleCallbacks[$event])) {
636 586
            return;
637
        }
638
639 177
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
640 177
            if ($arguments !== null) {
641 176
                call_user_func_array([$document, $callback], $arguments);
642
            } else {
643 177
                $document->$callback();
644
            }
645
        }
646 177
    }
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 803
    public function addLifecycleCallback($callback, $event)
680
    {
681 803
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
682 1
            return;
683
        }
684
685 803
        $this->lifecycleCallbacks[$event][] = $callback;
686 803
    }
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 887
    public function setLifecycleCallbacks(array $callbacks)
696
    {
697 887
        $this->lifecycleCallbacks = $callbacks;
698 887
    }
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 887
    public function setAlsoLoadMethods(array $methods)
722
    {
723 887
        $this->alsoLoadMethods = $methods;
724 887
    }
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 913
    public function setDiscriminatorField($discriminatorField)
739
    {
740 913
        if ($discriminatorField === null) {
741 845
            $this->discriminatorField = null;
742
743 845
            return;
744
        }
745
746
        // Handle array argument with name/fieldName keys for BC
747 117
        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 117
        foreach ($this->fieldMappings as $fieldMapping) {
756 4
            if ($discriminatorField === $fieldMapping['name']) {
757 4
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
758
            }
759
        }
760
761 116
        $this->discriminatorField = $discriminatorField;
762 116
    }
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 906
    public function setDiscriminatorMap(array $map)
773
    {
774 906
        foreach ($map as $value => $className) {
775 112
            $this->discriminatorMap[$value] = $className;
776 112
            if ($this->name === $className) {
777 104
                $this->discriminatorValue = $value;
778
            } else {
779 111
                if (! class_exists($className)) {
780
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
781
                }
782 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...
783 112
                    $this->subClasses[] = $className;
784
                }
785
            }
786
        }
787 906
    }
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 890
    public function setDefaultDiscriminatorValue($defaultDiscriminatorValue)
798
    {
799 890
        if ($defaultDiscriminatorValue === null) {
800 887
            $this->defaultDiscriminatorValue = null;
801
802 887
            return;
803
        }
804
805 48
        if (! array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
806
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
807
        }
808
809 48
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
810 48
    }
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 183
    public function addIndex($keys, array $options = [])
832
    {
833 183
        $this->indexes[] = [
834
            'keys' => array_map(function ($value) {
835 183
                if ($value === 1 || $value === -1) {
836 45
                    return (int) $value;
837
                }
838 183
                if (is_string($value)) {
839 183
                    $lower = strtolower($value);
840 183
                    if ($lower === 'asc') {
841 176
                        return 1;
842
                    }
843
844 52
                    if ($lower === 'desc') {
845
                        return -1;
846
                    }
847
                }
848 52
                return $value;
849 183
            }, $keys),
850 183
            'options' => $options,
851
        ];
852 183
    }
853
854
    /**
855
     * Returns the array of indexes for this Document.
856
     *
857
     * @return array $indexes The array of indexes.
858
     */
859 23
    public function getIndexes()
860
    {
861 23
        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 71
    public function setShardKey(array $keys, array $options = [])
883
    {
884 71
        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 71
        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 69
        foreach (array_keys($keys) as $field) {
893 69
            if (! isset($this->fieldMappings[$field])) {
894 62
                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 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...
907
            'keys' => array_map(function ($value) {
908 65
                if ($value === 1 || $value === -1) {
909 5
                    return (int) $value;
910
                }
911 65
                if (is_string($value)) {
912 65
                    $lower = strtolower($value);
913 65
                    if ($lower === 'asc') {
914 63
                        return 1;
915
                    }
916
917 47
                    if ($lower === 'desc') {
918
                        return -1;
919
                    }
920
                }
921 47
                return $value;
922 65
            }, $keys),
923 65
            'options' => $options,
924
        ];
925 65
    }
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 1099
    public function isSharded()
941
    {
942 1099
        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 887
    public function setReadPreference($readPreference, $tags)
952
    {
953 887
        $this->readPreference = $readPreference;
954 887
        $this->readPreferenceTags = $tags;
955 887
    }
956
957
    /**
958
     * Sets the write concern used by this class.
959
     *
960
     * @param string $writeConcern
961
     */
962 897
    public function setWriteConcern($writeConcern)
963
    {
964 897
        $this->writeConcern = $writeConcern;
965 897
    }
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 551
    public function hasWriteConcern()
981
    {
982 551
        return $this->writeConcern !== null;
983
    }
984
985
    /**
986
     * Sets the change tracking policy used by this class.
987
     *
988
     * @param int $policy
989
     */
990 889
    public function setChangeTrackingPolicy($policy)
991
    {
992 889
        $this->changeTrackingPolicy = $policy;
993 889
    }
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 571
    public function isChangeTrackingDeferredImplicit()
1011
    {
1012 571
        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 310
    public function isChangeTrackingNotify()
1021
    {
1022 310
        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 1375
    public function getName()
1051
    {
1052 1375
        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 1299
    public function getDatabase()
1061
    {
1062 1299
        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 92
    public function setDatabase($db)
1071
    {
1072 92
        $this->db = $db;
1073 92
    }
1074
1075
    /**
1076
     * Get the collection this Document is mapped to.
1077
     *
1078
     * @return string $collection The collection name.
1079
     */
1080 1300
    public function getCollection()
1081
    {
1082 1300
        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 1482
    public function setCollection($name)
1093
    {
1094 1482
        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 1482
            $this->collection = $name;
1104
        }
1105 1482
    }
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 1392
    private function applyStorageStrategy(array &$mapping)
1183
    {
1184 1392
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1185 1374
            return;
1186
        }
1187
1188
        switch (true) {
1189 1357
            case $mapping['type'] === 'int':
1190 1356
            case $mapping['type'] === 'float':
1191 820
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1192 820
                $allowedStrategies = [self::STORAGE_STRATEGY_SET, self::STORAGE_STRATEGY_INCREMENT];
1193 820
                break;
1194
1195 1356
            case $mapping['type'] === 'many':
1196 1080
                $defaultStrategy = CollectionHelper::DEFAULT_STRATEGY;
1197
                $allowedStrategies = [
1198 1080
                    self::STORAGE_STRATEGY_PUSH_ALL,
1199 1080
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1200 1080
                    self::STORAGE_STRATEGY_SET,
1201 1080
                    self::STORAGE_STRATEGY_SET_ARRAY,
1202 1080
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1203 1080
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1204
                ];
1205 1080
                break;
1206
1207
            default:
1208 1343
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1209 1343
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1210
        }
1211
1212 1357
        if (! isset($mapping['strategy'])) {
1213 1348
            $mapping['strategy'] = $defaultStrategy;
1214
        }
1215
1216 1357
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1217
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1218
        }
1219
1220 1357
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1221 1357
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1222 1
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1223
        }
1224 1356
    }
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 116
    public function addInheritedFieldMapping(array $fieldMapping)
1282
    {
1283 116
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1284
1285 116
        if (! isset($fieldMapping['association'])) {
1286 116
            return;
1287
        }
1288
1289 67
        $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1290 67
    }
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 68
    public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
1303
    {
1304 68
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1305 68
    }
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 1311
    public function setIdGenerator($generator)
1419
    {
1420 1311
        $this->idGenerator = $generator;
1421 1311
    }
1422
1423
    /**
1424
     * Casts the identifier to its portable PHP type.
1425
     *
1426
     * @param mixed $id
1427
     * @return mixed $id
1428
     */
1429 594
    public function getPHPIdentifierValue($id)
1430
    {
1431 594
        $idType = $this->fieldMappings[$this->identifier]['type'];
1432 594
        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 657
    public function getDatabaseIdentifierValue($id)
1442
    {
1443 657
        $idType = $this->fieldMappings[$this->identifier]['type'];
1444 657
        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 523
    public function setIdentifierValue($document, $id)
1456
    {
1457 523
        $id = $this->getPHPIdentifierValue($id);
1458 523
        $this->reflFields[$this->identifier]->setValue($document, $id);
1459 523
    }
1460
1461
    /**
1462
     * Gets the document identifier as a PHP type.
1463
     *
1464
     * @param object $document
1465
     * @return mixed $id
1466
     */
1467 606
    public function getIdentifierValue($document)
1468
    {
1469 606
        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 562
    public function getEmbeddedFieldsMappings()
1554
    {
1555 562
        return array_filter(
1556 562
            $this->associationMappings,
1557
            function ($assoc) {
1558 427
                return ! empty($assoc['embedded']);
1559 562
            }
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 497
    public function hasDiscriminator()
1605
    {
1606 497
        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 887
    public function setIdGeneratorType($generatorType)
1615
    {
1616 887
        $this->generatorType = $generatorType;
1617 887
    }
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 569
    public function isInheritanceTypeNone()
1633
    {
1634 569
        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 886
    public function isInheritanceTypeSingleCollection()
1643
    {
1644 886
        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 1366
    public function setParentClasses(array $classNames)
1677
    {
1678 1366
        $this->parentClasses = $classNames;
1679
1680 1366
        if (count($classNames) <= 0) {
1681 1365
            return;
1682
        }
1683
1684 100
        $this->rootDocumentName = array_pop($classNames);
1685 100
    }
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 67
    public function setVersionMapping(array &$mapping)
1736
    {
1737 67
        if ($mapping['type'] !== 'int' && $mapping['type'] !== 'date') {
1738 1
            throw LockException::invalidVersionFieldType($mapping['type']);
1739
        }
1740
1741 66
        $this->isVersioned  = true;
1742 66
        $this->versionField = $mapping['fieldName'];
1743 66
    }
1744
1745
    /**
1746
     * Sets whether this class is to be versioned for optimistic locking.
1747
     *
1748
     * @param bool $bool
1749
     */
1750 887
    public function setVersioned($bool)
1751
    {
1752 887
        $this->isVersioned = $bool;
1753 887
    }
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 887
    public function setVersionField($versionField)
1762
    {
1763 887
        $this->versionField = $versionField;
1764 887
    }
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 1407
    public function mapField(array $mapping)
1894
    {
1895 1407
        if (! isset($mapping['fieldName']) && isset($mapping['name'])) {
1896 8
            $mapping['fieldName'] = $mapping['name'];
1897
        }
1898 1407
        if (! isset($mapping['fieldName'])) {
1899
            throw MappingException::missingFieldName($this->name);
1900
        }
1901 1407
        if (! isset($mapping['name'])) {
1902 1398
            $mapping['name'] = $mapping['fieldName'];
1903
        }
1904 1407
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1905 1
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, $mapping['name']);
1906
        }
1907 1406
        if ($this->discriminatorField !== null && $this->discriminatorField === $mapping['name']) {
1908 1
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1909
        }
1910 1405
        if (isset($mapping['collectionClass'])) {
1911 53
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1912
        }
1913 1405
        if (! empty($mapping['collectionClass'])) {
1914 53
            $rColl = new \ReflectionClass($mapping['collectionClass']);
1915 53
            if (! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1916 1
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1917
            }
1918
        }
1919
1920 1404
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1921 1
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1922
        }
1923
1924 1403
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : [];
1925
1926 1403
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
1927 1107
            $cascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1928
        }
1929
1930 1403
        if (isset($mapping['embedded'])) {
1931 1067
            unset($mapping['cascade']);
1932 1398
        } elseif (isset($mapping['cascade'])) {
1933 904
            $mapping['cascade'] = $cascades;
1934
        }
1935
1936 1403
        $mapping['isCascadeRemove'] = in_array('remove', $cascades);
1937 1403
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1938 1403
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1939 1403
        $mapping['isCascadeMerge'] = in_array('merge', $cascades);
1940 1403
        $mapping['isCascadeDetach'] = in_array('detach', $cascades);
1941
1942 1403
        if (isset($mapping['id']) && $mapping['id'] === true) {
1943 1372
            $mapping['name'] = '_id';
1944 1372
            $this->identifier = $mapping['fieldName'];
1945 1372
            if (isset($mapping['strategy'])) {
1946 1366
                $this->generatorType = constant(self::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
1947
            }
1948 1372
            $this->generatorOptions = $mapping['options'] ?? [];
1949 1372
            switch ($this->generatorType) {
1950 1372
                case self::GENERATOR_TYPE_AUTO:
1951 1300
                    $mapping['type'] = 'id';
1952 1300
                    break;
1953
                default:
1954 145
                    if (! empty($this->generatorOptions['type'])) {
1955 56
                        $mapping['type'] = $this->generatorOptions['type'];
1956 89
                    } elseif (empty($mapping['type'])) {
1957 77
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
1958
                    }
1959
            }
1960 1372
            unset($this->generatorOptions['type']);
1961
        }
1962
1963 1403
        if (! isset($mapping['nullable'])) {
1964 38
            $mapping['nullable'] = false;
1965
        }
1966
1967 1403
        if (isset($mapping['reference'])
1968 1403
            && isset($mapping['storeAs'])
1969 1403
            && $mapping['storeAs'] === self::REFERENCE_STORE_AS_ID
1970 1403
            && ! isset($mapping['targetDocument'])
1971
        ) {
1972 3
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
1973
        }
1974
1975 1400
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
1976 1400
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
1977 4
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
1978
        }
1979
1980 1396
        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 1395
        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 1392
        if (isset($mapping['reference']) && $mapping['type'] === 'one') {
1989 1000
            $mapping['association'] = self::REFERENCE_ONE;
1990
        }
1991 1392
        if (isset($mapping['reference']) && $mapping['type'] === 'many') {
1992 955
            $mapping['association'] = self::REFERENCE_MANY;
1993
        }
1994 1392
        if (isset($mapping['embedded']) && $mapping['type'] === 'one') {
1995 944
            $mapping['association'] = self::EMBED_ONE;
1996
        }
1997 1392
        if (isset($mapping['embedded']) && $mapping['type'] === 'many') {
1998 978
            $mapping['association'] = self::EMBED_MANY;
1999
        }
2000
2001 1392
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
2002 124
            $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 1392
        if (isset($mapping['version'])) {
2011 67
            $mapping['notSaved'] = true;
2012 67
            $this->setVersionMapping($mapping);
2013
        }
2014 1392
        if (isset($mapping['lock'])) {
2015 22
            $mapping['notSaved'] = true;
2016 22
            $this->setLockMapping($mapping);
2017
        }
2018 1392
        $mapping['isOwningSide'] = true;
2019 1392
        $mapping['isInverseSide'] = false;
2020 1392
        if (isset($mapping['reference'])) {
2021 1065
            if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
2022 84
                $mapping['isOwningSide'] = true;
2023 84
                $mapping['isInverseSide'] = false;
2024
            }
2025 1065
            if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
2026 814
                $mapping['isInverseSide'] = true;
2027 814
                $mapping['isOwningSide'] = false;
2028
            }
2029 1065
            if (isset($mapping['repositoryMethod'])) {
2030 58
                $mapping['isInverseSide'] = true;
2031 58
                $mapping['isOwningSide'] = false;
2032
            }
2033 1065
            if (! isset($mapping['orphanRemoval'])) {
2034 1045
                $mapping['orphanRemoval'] = false;
2035
            }
2036
        }
2037
2038 1392
        if (! empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || ! $mapping['isInverseSide'])) {
2039
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
2040
        }
2041
2042 1392
        $this->applyStorageStrategy($mapping);
2043
2044 1391
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
2045 1391
        if (isset($mapping['association'])) {
2046 1199
            $this->associationMappings[$mapping['fieldName']] = $mapping;
2047
        }
2048
2049 1391
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
2050 1390
        $reflProp->setAccessible(true);
2051 1390
        $this->reflFields[$mapping['fieldName']] = $reflProp;
2052
2053 1390
        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