Completed
Pull Request — master (#1693)
by Andreas
15:14
created

ClassMetadata::setShardKey()   C

Complexity

Conditions 13
Paths 7

Size

Total Lines 42
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 13

Importance

Changes 0
Metric Value
dl 0
loc 42
ccs 26
cts 26
cp 1
rs 5.1234
c 0
b 0
f 0
cc 13
eloc 25
nc 7
nop 2
crap 13

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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