Completed
Push — master ( f0fe3a...13ea99 )
by Andreas
12s
created

ClassMetadata::getBucketName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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