Completed
Pull Request — master (#1790)
by Andreas
17:27 queued 13:11
created

ClassMetadata::setDiscriminatorMap()   C

Complexity

Conditions 8
Paths 10

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 8.1867

Importance

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