Completed
Push — master ( 7b9f4b...1cd743 )
by Andreas
13s queued 10s
created

lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php (9 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Mapping;
6
7
use BadMethodCallException;
8
use Doctrine\Common\Persistence\Mapping\ClassMetadata as BaseClassMetadata;
9
use Doctrine\Instantiator\Instantiator;
10
use Doctrine\Instantiator\InstantiatorInterface;
11
use Doctrine\ODM\MongoDB\Id\AbstractIdGenerator;
12
use Doctrine\ODM\MongoDB\LockException;
13
use Doctrine\ODM\MongoDB\Types\Type;
14
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
15
use InvalidArgumentException;
16
use LogicException;
17
use ProxyManager\Proxy\GhostObjectInterface;
18
use ReflectionClass;
19
use ReflectionProperty;
20
use function array_filter;
21
use function array_key_exists;
22
use function array_keys;
23
use function array_map;
24
use function array_pop;
25
use function class_exists;
26
use function constant;
27
use function count;
28
use function get_class;
29
use function in_array;
30
use function is_array;
31
use function is_string;
32
use function is_subclass_of;
33
use function ltrim;
34
use function sprintf;
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
class ClassMetadata implements BaseClassMetadata
53
{
54
    /* The Id generator types. */
55
    /**
56
     * AUTO means Doctrine will automatically create a new \MongoDB\BSON\ObjectId instance for us.
57
     */
58
    public const GENERATOR_TYPE_AUTO = 1;
59
60
    /**
61
     * INCREMENT means a separate collection is used for maintaining and incrementing id generation.
62
     * Offers full portability.
63
     */
64
    public const GENERATOR_TYPE_INCREMENT = 2;
65
66
    /**
67
     * UUID means Doctrine will generate a uuid for us.
68
     */
69
    public const GENERATOR_TYPE_UUID = 3;
70
71
    /**
72
     * ALNUM means Doctrine will generate Alpha-numeric string identifiers, using the INCREMENT
73
     * generator to ensure identifier uniqueness
74
     */
75
    public const GENERATOR_TYPE_ALNUM = 4;
76
77
    /**
78
     * CUSTOM means Doctrine expect a class parameter. It will then try to initiate that class
79
     * and pass other options to the generator. It will throw an Exception if the class
80
     * does not exist or if an option was passed for that there is not setter in the new
81
     * generator class.
82
     *
83
     * The class  will have to be a subtype of AbstractIdGenerator.
84
     */
85
    public const GENERATOR_TYPE_CUSTOM = 5;
86
87
    /**
88
     * NONE means Doctrine will not generate any id for us and you are responsible for manually
89
     * assigning an id.
90
     */
91
    public const GENERATOR_TYPE_NONE = 6;
92
93
    /**
94
     * Default discriminator field name.
95
     *
96
     * This is used for associations value for associations where a that do not define a "targetDocument" or
97
     * "discriminatorField" option in their mapping.
98
     */
99
    public const DEFAULT_DISCRIMINATOR_FIELD = '_doctrine_class_name';
100
101
    public const REFERENCE_ONE  = 1;
102
    public const REFERENCE_MANY = 2;
103
    public const EMBED_ONE      = 3;
104
    public const EMBED_MANY     = 4;
105
    public const MANY           = 'many';
106
    public const ONE            = 'one';
107
108
    /**
109
     * The types of storeAs references
110
     */
111
    public const REFERENCE_STORE_AS_ID             = 'id';
112
    public const REFERENCE_STORE_AS_DB_REF         = 'dbRef';
113
    public const REFERENCE_STORE_AS_DB_REF_WITH_DB = 'dbRefWithDb';
114
    public const REFERENCE_STORE_AS_REF            = 'ref';
115
116
    /* The inheritance mapping types */
117
    /**
118
     * NONE means the class does not participate in an inheritance hierarchy
119
     * and therefore does not need an inheritance mapping type.
120
     */
121
    public const INHERITANCE_TYPE_NONE = 1;
122
123
    /**
124
     * SINGLE_COLLECTION means the class will be persisted according to the rules of
125
     * <tt>Single Collection Inheritance</tt>.
126
     */
127
    public const INHERITANCE_TYPE_SINGLE_COLLECTION = 2;
128
129
    /**
130
     * COLLECTION_PER_CLASS means the class will be persisted according to the rules
131
     * of <tt>Concrete Collection Inheritance</tt>.
132
     */
133
    public const INHERITANCE_TYPE_COLLECTION_PER_CLASS = 3;
134
135
    /**
136
     * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time
137
     * by doing a property-by-property comparison with the original data. This will
138
     * be done for all entities that are in MANAGED state at commit-time.
139
     *
140
     * This is the default change tracking policy.
141
     */
142
    public const CHANGETRACKING_DEFERRED_IMPLICIT = 1;
143
144
    /**
145
     * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time
146
     * by doing a property-by-property comparison with the original data. This will
147
     * be done only for entities that were explicitly saved (through persist() or a cascade).
148
     */
149
    public const CHANGETRACKING_DEFERRED_EXPLICIT = 2;
150
151
    /**
152
     * NOTIFY means that Doctrine relies on the entities sending out notifications
153
     * when their properties change. Such entity classes must implement
154
     * the <tt>NotifyPropertyChanged</tt> interface.
155
     */
156
    public const CHANGETRACKING_NOTIFY = 3;
157
158
    /**
159
     * SET means that fields will be written to the database using a $set operator
160
     */
161
    public const STORAGE_STRATEGY_SET = 'set';
162
163
    /**
164
     * INCREMENT means that fields will be written to the database by calculating
165
     * the difference and using the $inc operator
166
     */
167
    public const STORAGE_STRATEGY_INCREMENT = 'increment';
168
169
    public const STORAGE_STRATEGY_PUSH_ALL         = 'pushAll';
170
    public const STORAGE_STRATEGY_ADD_TO_SET       = 'addToSet';
171
    public const STORAGE_STRATEGY_ATOMIC_SET       = 'atomicSet';
172
    public const STORAGE_STRATEGY_ATOMIC_SET_ARRAY = 'atomicSetArray';
173
    public const STORAGE_STRATEGY_SET_ARRAY        = 'setArray';
174
175
    private const ALLOWED_GRIDFS_FIELDS = ['_id', 'chunkSize', 'filename', 'length', 'metadata', 'uploadDate'];
176
177
    /**
178
     * READ-ONLY: The name of the mongo database the document is mapped to.
179
     *
180
     * @var string|null
181
     */
182
    public $db;
183
184
    /**
185
     * READ-ONLY: The name of the mongo collection the document is mapped to.
186
     *
187
     * @var string
188
     */
189
    public $collection;
190
191
    /**
192
     * READ-ONLY: The name of the GridFS bucket the document is mapped to.
193
     *
194
     * @var string
195
     */
196
    public $bucketName = 'fs';
197
198
    /**
199
     * READ-ONLY: If the collection should be a fixed size.
200
     *
201
     * @var bool
202
     */
203
    public $collectionCapped = false;
204
205
    /**
206
     * READ-ONLY: If the collection is fixed size, its size in bytes.
207
     *
208
     * @var int|null
209
     */
210
    public $collectionSize;
211
212
    /**
213
     * READ-ONLY: If the collection is fixed size, the maximum number of elements to store in the collection.
214
     *
215
     * @var int|null
216
     */
217
    public $collectionMax;
218
219
    /**
220
     * READ-ONLY Describes how MongoDB clients route read operations to the members of a replica set.
221
     *
222
     * @var string|null
223
     */
224
    public $readPreference;
225
226
    /**
227
     * READ-ONLY Associated with readPreference Allows to specify criteria so that your application can target read
228
     * operations to specific members, based on custom parameters.
229
     *
230
     * @var string[][]
231
     */
232
    public $readPreferenceTags = [];
233
234
    /**
235
     * READ-ONLY: Describes the level of acknowledgement requested from MongoDB for write operations.
236
     *
237
     * @var string|int|null
238
     */
239
    public $writeConcern;
240
241
    /**
242
     * READ-ONLY: The field name of the document identifier.
243
     *
244
     * @var string|null
245
     */
246
    public $identifier;
247
248
    /**
249
     * READ-ONLY: The array of indexes for the document collection.
250
     *
251
     * @var array
252
     */
253
    public $indexes = [];
254
255
    /**
256
     * READ-ONLY: Keys and options describing shard key. Only for sharded collections.
257
     *
258
     * @var array<string, array>
259
     */
260
    public $shardKey = [];
261
262
    /**
263
     * READ-ONLY: The name of the document class.
264
     *
265
     * @var string
266
     */
267
    public $name;
268
269
    /**
270
     * READ-ONLY: The name of the document class that is at the root of the mapped document inheritance
271
     * hierarchy. If the document is not part of a mapped inheritance hierarchy this is the same
272
     * as {@link $documentName}.
273
     *
274
     * @var string
275
     */
276
    public $rootDocumentName;
277
278
    /**
279
     * The name of the custom repository class used for the document class.
280
     * (Optional).
281
     *
282
     * @var string|null
283
     */
284
    public $customRepositoryClassName;
285
286
    /**
287
     * READ-ONLY: The names of the parent classes (ancestors).
288
     *
289
     * @var array
290
     */
291
    public $parentClasses = [];
292
293
    /**
294
     * READ-ONLY: The names of all subclasses (descendants).
295
     *
296
     * @var array
297
     */
298
    public $subClasses = [];
299
300
    /**
301
     * The ReflectionProperty instances of the mapped class.
302
     *
303
     * @var ReflectionProperty[]
304
     */
305
    public $reflFields = [];
306
307
    /**
308
     * READ-ONLY: The inheritance mapping type used by the class.
309
     *
310
     * @var int
311
     */
312
    public $inheritanceType = self::INHERITANCE_TYPE_NONE;
313
314
    /**
315
     * READ-ONLY: The Id generator type used by the class.
316
     *
317
     * @var int
318
     */
319
    public $generatorType = self::GENERATOR_TYPE_AUTO;
320
321
    /**
322
     * READ-ONLY: The Id generator options.
323
     *
324
     * @var array
325
     */
326
    public $generatorOptions = [];
327
328
    /**
329
     * READ-ONLY: The ID generator used for generating IDs for this class.
330
     *
331
     * @var AbstractIdGenerator|null
332
     */
333
    public $idGenerator;
334
335
    /**
336
     * READ-ONLY: The field mappings of the class.
337
     * Keys are field names and values are mapping definitions.
338
     *
339
     * The mapping definition array has the following values:
340
     *
341
     * - <b>fieldName</b> (string)
342
     * The name of the field in the Document.
343
     *
344
     * - <b>id</b> (boolean, optional)
345
     * Marks the field as the primary key of the document. Multiple fields of an
346
     * document can have the id attribute, forming a composite key.
347
     *
348
     * @var array
349
     */
350
    public $fieldMappings = [];
351
352
    /**
353
     * READ-ONLY: The association mappings of the class.
354
     * Keys are field names and values are mapping definitions.
355
     *
356
     * @var array
357
     */
358
    public $associationMappings = [];
359
360
    /**
361
     * READ-ONLY: Array of fields to also load with a given method.
362
     *
363
     * @var array
364
     */
365
    public $alsoLoadMethods = [];
366
367
    /**
368
     * READ-ONLY: The registered lifecycle callbacks for documents of this class.
369
     *
370
     * @var array
371
     */
372
    public $lifecycleCallbacks = [];
373
374
    /**
375
     * READ-ONLY: The discriminator value of this class.
376
     *
377
     * <b>This does only apply to the JOINED and SINGLE_COLLECTION inheritance mapping strategies
378
     * where a discriminator field is used.</b>
379
     *
380
     * @see discriminatorField
381
     *
382
     * @var mixed
383
     */
384
    public $discriminatorValue;
385
386
    /**
387
     * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
388
     *
389
     * <b>This does only apply to the SINGLE_COLLECTION inheritance mapping strategy
390
     * where a discriminator field is used.</b>
391
     *
392
     * @see discriminatorField
393
     *
394
     * @var mixed
395
     */
396
    public $discriminatorMap = [];
397
398
    /**
399
     * READ-ONLY: The definition of the discriminator field used in SINGLE_COLLECTION
400
     * inheritance mapping.
401
     *
402
     * @var string|null
403
     */
404
    public $discriminatorField;
405
406
    /**
407
     * READ-ONLY: The default value for discriminatorField in case it's not set in the document
408
     *
409
     * @see discriminatorField
410
     *
411
     * @var string|null
412
     */
413
    public $defaultDiscriminatorValue;
414
415
    /**
416
     * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
417
     *
418
     * @var bool
419
     */
420
    public $isMappedSuperclass = false;
421
422
    /**
423
     * READ-ONLY: Whether this class describes the mapping of a embedded document.
424
     *
425
     * @var bool
426
     */
427
    public $isEmbeddedDocument = false;
428
429
    /**
430
     * READ-ONLY: Whether this class describes the mapping of an aggregation result document.
431
     *
432
     * @var bool
433
     */
434
    public $isQueryResultDocument = false;
435
436
    /**
437
     * READ-ONLY: Whether this class describes the mapping of a gridFS file
438
     *
439
     * @var bool
440
     */
441
    public $isFile = false;
442
443
    /**
444
     * READ-ONLY: The default chunk size in bytes for the file
445
     *
446
     * @var int|null
447
     */
448
    public $chunkSizeBytes;
449
450
    /**
451
     * READ-ONLY: The policy used for change-tracking on entities of this class.
452
     *
453
     * @var int
454
     */
455
    public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;
456
457
    /**
458
     * READ-ONLY: A flag for whether or not instances of this class are to be versioned
459
     * with optimistic locking.
460
     *
461
     * @var bool $isVersioned
462
     */
463
    public $isVersioned = false;
464
465
    /**
466
     * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
467
     *
468
     * @var string|null $versionField
469
     */
470
    public $versionField;
471
472
    /**
473
     * READ-ONLY: A flag for whether or not instances of this class are to allow pessimistic
474
     * locking.
475
     *
476
     * @var bool $isLockable
477
     */
478
    public $isLockable = false;
479
480
    /**
481
     * READ-ONLY: The name of the field which is used for locking a document.
482
     *
483
     * @var mixed $lockField
484
     */
485
    public $lockField;
486
487
    /**
488
     * The ReflectionClass instance of the mapped class.
489
     *
490
     * @var ReflectionClass
491
     */
492
    public $reflClass;
493
494
    /**
495
     * READ_ONLY: A flag for whether or not this document is read-only.
496
     *
497
     * @var bool
498
     */
499
    public $isReadOnly;
500
501
    /** @var InstantiatorInterface */
502
    private $instantiator;
503
504
    /**
505
     * Initializes a new ClassMetadata instance that will hold the object-document mapping
506
     * metadata of the class with the given name.
507
     */
508 1609
    public function __construct(string $documentName)
509
    {
510 1609
        $this->name             = $documentName;
511 1609
        $this->rootDocumentName = $documentName;
512 1609
        $this->reflClass        = new ReflectionClass($documentName);
513 1609
        $this->setCollection($this->reflClass->getShortName());
514 1609
        $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...\InstantiatorInterface> 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...
515 1609
    }
516
517
    /**
518
     * Helper method to get reference id of ref* type references
519
     *
520
     * @internal
521
     *
522
     * @param mixed $reference
523
     *
524
     * @return mixed
525
     */
526 116
    public static function getReferenceId($reference, string $storeAs)
527
    {
528 116
        return $storeAs === self::REFERENCE_STORE_AS_ID ? $reference : $reference[self::getReferencePrefix($storeAs) . 'id'];
529
    }
530
531
    /**
532
     * Returns the reference prefix used for a reference
533
     */
534 184
    private static function getReferencePrefix(string $storeAs) : string
535
    {
536 184
        if (! in_array($storeAs, [self::REFERENCE_STORE_AS_REF, self::REFERENCE_STORE_AS_DB_REF, self::REFERENCE_STORE_AS_DB_REF_WITH_DB])) {
537
            throw new LogicException('Can only get a reference prefix for DBRef and reference arrays');
538
        }
539
540 184
        return $storeAs === self::REFERENCE_STORE_AS_REF ? '' : '$';
541
    }
542
543
    /**
544
     * Returns a fully qualified field name for a given reference
545
     *
546
     * @internal
547
     *
548
     * @param string $pathPrefix The field path prefix
549
     */
550 140
    public static function getReferenceFieldName(string $storeAs, string $pathPrefix = '') : string
551
    {
552 140
        if ($storeAs === self::REFERENCE_STORE_AS_ID) {
553 100
            return $pathPrefix;
554
        }
555
556 125
        return ($pathPrefix ? $pathPrefix . '.' : '') . static::getReferencePrefix($storeAs) . 'id';
557
    }
558
559
    /**
560
     * {@inheritDoc}
561
     */
562 1493
    public function getReflectionClass() : ReflectionClass
563
    {
564 1493
        return $this->reflClass;
565
    }
566
567
    /**
568
     * {@inheritDoc}
569
     */
570 323
    public function isIdentifier($fieldName) : bool
571
    {
572 323
        return $this->identifier === $fieldName;
573
    }
574
575
    /**
576
     * INTERNAL:
577
     * Sets the mapped identifier field of this class.
578
     */
579 959
    public function setIdentifier(?string $identifier) : void
580
    {
581 959
        $this->identifier = $identifier;
582 959
    }
583
584
    /**
585
     * {@inheritDoc}
586
     *
587
     * Since MongoDB only allows exactly one identifier field
588
     * this will always return an array with only one value
589
     */
590 11
    public function getIdentifier() : array
591
    {
592 11
        return [$this->identifier];
593
    }
594
595
    /**
596
     * Since MongoDB only allows exactly one identifier field
597
     * this will always return an array with only one value
598
     *
599
     * return (string|null)[]
600
     */
601 99
    public function getIdentifierFieldNames() : array
602
    {
603 99
        return [$this->identifier];
604
    }
605
606
    /**
607
     * {@inheritDoc}
608
     */
609 932
    public function hasField($fieldName) : bool
610
    {
611 932
        return isset($this->fieldMappings[$fieldName]);
612
    }
613
614
    /**
615
     * Sets the inheritance type used by the class and it's subclasses.
616
     */
617 975
    public function setInheritanceType(int $type) : void
618
    {
619 975
        $this->inheritanceType = $type;
620 975
    }
621
622
    /**
623
     * Checks whether a mapped field is inherited from an entity superclass.
624
     */
625 1484
    public function isInheritedField(string $fieldName) : bool
626
    {
627 1484
        return isset($this->fieldMappings[$fieldName]['inherited']);
628
    }
629
630
    /**
631
     * Registers a custom repository class for the document class.
632
     */
633 906
    public function setCustomRepositoryClass(?string $repositoryClassName) : void
634
    {
635 906
        if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
636
            return;
637
        }
638
639 906
        $this->customRepositoryClassName = $repositoryClassName;
640 906
    }
641
642
    /**
643
     * Dispatches the lifecycle event of the given document by invoking all
644
     * registered callbacks.
645
     *
646
     * @throws InvalidArgumentException If document class is not this class or
647
     *                                   a Proxy of this class.
648
     */
649 645
    public function invokeLifecycleCallbacks(string $event, object $document, ?array $arguments = null) : void
650
    {
651 645
        if (! $document instanceof $this->name) {
652 1
            throw new InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"', $this->name, get_class($document)));
653
        }
654
655 644
        if (empty($this->lifecycleCallbacks[$event])) {
656 629
            return;
657
        }
658
659 189
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
660 189
            if ($arguments !== null) {
661 188
                $document->$callback(...$arguments);
662
            } else {
663 2
                $document->$callback();
664
            }
665
        }
666 189
    }
667
668
    /**
669
     * Checks whether the class has callbacks registered for a lifecycle event.
670
     */
671
    public function hasLifecycleCallbacks(string $event) : bool
672
    {
673
        return ! empty($this->lifecycleCallbacks[$event]);
674
    }
675
676
    /**
677
     * Gets the registered lifecycle callbacks for an event.
678
     */
679
    public function getLifecycleCallbacks(string $event) : array
680
    {
681
        return $this->lifecycleCallbacks[$event] ?? [];
682
    }
683
684
    /**
685
     * Adds a lifecycle callback for documents of this class.
686
     *
687
     * If the callback is already registered, this is a NOOP.
688
     */
689 877
    public function addLifecycleCallback(string $callback, string $event) : void
690
    {
691 877
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
692 1
            return;
693
        }
694
695 877
        $this->lifecycleCallbacks[$event][] = $callback;
696 877
    }
697
698
    /**
699
     * Sets the lifecycle callbacks for documents of this class.
700
     *
701
     * Any previously registered callbacks are overwritten.
702
     */
703 958
    public function setLifecycleCallbacks(array $callbacks) : void
704
    {
705 958
        $this->lifecycleCallbacks = $callbacks;
706 958
    }
707
708
    /**
709
     * Registers a method for loading document data before field hydration.
710
     *
711
     * Note: A method may be registered multiple times for different fields.
712
     * it will be invoked only once for the first field found.
713
     *
714
     * @param array|string $fields Database field name(s)
715
     */
716 14
    public function registerAlsoLoadMethod(string $method, $fields) : void
717
    {
718 14
        $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : [$fields];
719 14
    }
720
721
    /**
722
     * Sets the AlsoLoad methods for documents of this class.
723
     *
724
     * Any previously registered methods are overwritten.
725
     */
726 958
    public function setAlsoLoadMethods(array $methods) : void
727
    {
728 958
        $this->alsoLoadMethods = $methods;
729 958
    }
730
731
    /**
732
     * Sets the discriminator field.
733
     *
734
     * The field name is the the unmapped database field. Discriminator values
735
     * are only used to discern the hydration class and are not mapped to class
736
     * properties.
737
     *
738
     * @param array|string|null $discriminatorField
739
     *
740
     * @throws MappingException If the discriminator field conflicts with the
741
     *                          "name" attribute of a mapped field.
742
     */
743 984
    public function setDiscriminatorField($discriminatorField) : void
744
    {
745 984
        if ($this->isFile) {
746
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
747
        }
748
749 984
        if ($discriminatorField === null) {
750 915
            $this->discriminatorField = null;
751
752 915
            return;
753
        }
754
755
        // @todo: deprecate, document and remove this:
756
        // Handle array argument with name/fieldName keys for BC
757 181
        if (is_array($discriminatorField)) {
758
            if (isset($discriminatorField['name'])) {
759
                $discriminatorField = $discriminatorField['name'];
760
            } elseif (isset($discriminatorField['fieldName'])) {
761
                $discriminatorField = $discriminatorField['fieldName'];
762
            }
763
        }
764
765 181
        foreach ($this->fieldMappings as $fieldMapping) {
766 4
            if ($discriminatorField === $fieldMapping['name']) {
767 1
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
768
            }
769
        }
770
771 180
        $this->discriminatorField = $discriminatorField;
772 180
    }
773
774
    /**
775
     * Sets the discriminator values used by this class.
776
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
777
     *
778
     * @throws MappingException
779
     */
780 977
    public function setDiscriminatorMap(array $map) : void
781
    {
782 977
        if ($this->isFile) {
783
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
784
        }
785
786 977
        foreach ($map as $value => $className) {
787 175
            $this->discriminatorMap[$value] = $className;
788 175
            if ($this->name === $className) {
789 167
                $this->discriminatorValue = $value;
790
            } else {
791 174
                if (! class_exists($className)) {
792
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
793
                }
794 174
                if (is_subclass_of($className, $this->name)) {
795 160
                    $this->subClasses[] = $className;
796
                }
797
            }
798
        }
799 977
    }
800
801
    /**
802
     * Sets the default discriminator value to be used for this class
803
     * Used for SINGLE_TABLE inheritance mapping strategies if the document has no discriminator value
804
     *
805
     * @throws MappingException
806
     */
807 961
    public function setDefaultDiscriminatorValue(?string $defaultDiscriminatorValue) : void
808
    {
809 961
        if ($this->isFile) {
810
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
811
        }
812
813 961
        if ($defaultDiscriminatorValue === null) {
814 958
            $this->defaultDiscriminatorValue = null;
815
816 958
            return;
817
        }
818
819 111
        if (! array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
820
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
821
        }
822
823 111
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
824 111
    }
825
826
    /**
827
     * Sets the discriminator value for this class.
828
     * Used for JOINED/SINGLE_TABLE inheritance and multiple document types in a single
829
     * collection.
830
     *
831
     * @throws MappingException
832
     */
833 3
    public function setDiscriminatorValue(string $value) : void
834
    {
835 3
        if ($this->isFile) {
836
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
837
        }
838
839 3
        $this->discriminatorMap[$value] = $this->name;
840 3
        $this->discriminatorValue       = $value;
841 3
    }
842
843
    /**
844
     * Add a index for this Document.
845
     */
846 259
    public function addIndex(array $keys, array $options = []) : void
847
    {
848 259
        $this->indexes[] = [
849
            'keys' => array_map(static function ($value) {
850 259
                if ($value === 1 || $value === -1) {
851 108
                    return $value;
852
                }
853 259
                if (is_string($value)) {
854 259
                    $lower = strtolower($value);
855 259
                    if ($lower === 'asc') {
856 252
                        return 1;
857
                    }
858
859 115
                    if ($lower === 'desc') {
860
                        return -1;
861
                    }
862
                }
863 115
                return $value;
864 259
            }, $keys),
865 259
            'options' => $options,
866
        ];
867 259
    }
868
869
    /**
870
     * Returns the array of indexes for this Document.
871
     */
872 43
    public function getIndexes() : array
873
    {
874 43
        return $this->indexes;
875
    }
876
877
    /**
878
     * Checks whether this document has indexes or not.
879
     */
880
    public function hasIndexes() : bool
881
    {
882
        return $this->indexes ? true : false;
883
    }
884
885
    /**
886
     * Set shard key for this Document.
887
     *
888
     * @throws MappingException
889
     */
890 143
    public function setShardKey(array $keys, array $options = []) : void
891
    {
892 143
        if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && $this->shardKey !== []) {
893 2
            throw MappingException::shardKeyInSingleCollInheritanceSubclass($this->getName());
0 ignored issues
show
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
894
        }
895
896 143
        if ($this->isEmbeddedDocument) {
897 2
            throw MappingException::embeddedDocumentCantHaveShardKey($this->getName());
0 ignored issues
show
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
898
        }
899
900 141
        foreach (array_keys($keys) as $field) {
901 141
            if (! isset($this->fieldMappings[$field])) {
902 134
                continue;
903
            }
904
905 7
            if (in_array($this->fieldMappings[$field]['type'], ['many', 'collection'])) {
906 3
                throw MappingException::noMultiKeyShardKeys($this->getName(), $field);
0 ignored issues
show
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
907
            }
908
909 4
            if ($this->fieldMappings[$field]['strategy'] !== static::STORAGE_STRATEGY_SET) {
910 1
                throw MappingException::onlySetStrategyAllowedInShardKey($this->getName(), $field);
0 ignored issues
show
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
911
            }
912
        }
913
914 137
        $this->shardKey = [
915
            'keys' => array_map(static function ($value) {
916 137
                if ($value === 1 || $value === -1) {
917 5
                    return $value;
918
                }
919 137
                if (is_string($value)) {
920 137
                    $lower = strtolower($value);
921 137
                    if ($lower === 'asc') {
922 135
                        return 1;
923
                    }
924
925 110
                    if ($lower === 'desc') {
926
                        return -1;
927
                    }
928
                }
929 110
                return $value;
930 137
            }, $keys),
931 137
            'options' => $options,
932
        ];
933 137
    }
934
935 26
    public function getShardKey() : array
936
    {
937 26
        return $this->shardKey;
938
    }
939
940
    /**
941
     * Checks whether this document has shard key or not.
942
     */
943 1204
    public function isSharded() : bool
944
    {
945 1204
        return $this->shardKey ? true : false;
946
    }
947
948
    /**
949
     * Sets the read preference used by this class.
950
     */
951 958
    public function setReadPreference(?string $readPreference, array $tags) : void
952
    {
953 958
        $this->readPreference     = $readPreference;
954 958
        $this->readPreferenceTags = $tags;
955 958
    }
956
957
    /**
958
     * Sets the write concern used by this class.
959
     *
960
     * @param string|int|null $writeConcern
961
     */
962 968
    public function setWriteConcern($writeConcern) : void
963
    {
964 968
        $this->writeConcern = $writeConcern;
965 968
    }
966
967
    /**
968
     * @return int|string|null
969
     */
970 11
    public function getWriteConcern()
971
    {
972 11
        return $this->writeConcern;
973
    }
974
975
    /**
976
     * Whether there is a write concern configured for this class.
977
     */
978 592
    public function hasWriteConcern() : bool
979
    {
980 592
        return $this->writeConcern !== null;
981
    }
982
983
    /**
984
     * Sets the change tracking policy used by this class.
985
     */
986 960
    public function setChangeTrackingPolicy(int $policy) : void
987
    {
988 960
        $this->changeTrackingPolicy = $policy;
989 960
    }
990
991
    /**
992
     * Whether the change tracking policy of this class is "deferred explicit".
993
     */
994 68
    public function isChangeTrackingDeferredExplicit() : bool
995
    {
996 68
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT;
997
    }
998
999
    /**
1000
     * Whether the change tracking policy of this class is "deferred implicit".
1001
     */
1002 615
    public function isChangeTrackingDeferredImplicit() : bool
1003
    {
1004 615
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT;
1005
    }
1006
1007
    /**
1008
     * Whether the change tracking policy of this class is "notify".
1009
     */
1010 342
    public function isChangeTrackingNotify() : bool
1011
    {
1012 342
        return $this->changeTrackingPolicy === self::CHANGETRACKING_NOTIFY;
1013
    }
1014
1015
    /**
1016
     * Gets the ReflectionProperties of the mapped class.
1017
     */
1018 1
    public function getReflectionProperties() : array
1019
    {
1020 1
        return $this->reflFields;
1021
    }
1022
1023
    /**
1024
     * Gets a ReflectionProperty for a specific field of the mapped class.
1025
     */
1026 98
    public function getReflectionProperty(string $name) : ReflectionProperty
1027
    {
1028 98
        return $this->reflFields[$name];
1029
    }
1030
1031
    /**
1032
     * {@inheritDoc}
1033
     */
1034 1492
    public function getName() : string
1035
    {
1036 1492
        return $this->name;
1037
    }
1038
1039
    /**
1040
     * Returns the database this Document is mapped to.
1041
     */
1042 1409
    public function getDatabase() : ?string
1043
    {
1044 1409
        return $this->db;
1045
    }
1046
1047
    /**
1048
     * Set the database this Document is mapped to.
1049
     */
1050 154
    public function setDatabase(?string $db) : void
1051
    {
1052 154
        $this->db = $db;
1053 154
    }
1054
1055
    /**
1056
     * Get the collection this Document is mapped to.
1057
     */
1058 1401
    public function getCollection() : string
1059
    {
1060 1401
        return $this->collection;
1061
    }
1062
1063
    /**
1064
     * Sets the collection this Document is mapped to.
1065
     *
1066
     * @param array|string $name
1067
     *
1068
     * @throws InvalidArgumentException
1069
     */
1070 1609
    public function setCollection($name) : void
1071
    {
1072 1609
        if (is_array($name)) {
1073 1
            if (! isset($name['name'])) {
1074
                throw new InvalidArgumentException('A name key is required when passing an array to setCollection()');
1075
            }
1076 1
            $this->collectionCapped = $name['capped'] ?? false;
1077 1
            $this->collectionSize   = $name['size'] ?? 0;
1078 1
            $this->collectionMax    = $name['max'] ?? 0;
1079 1
            $this->collection       = $name['name'];
1080
        } else {
1081 1609
            $this->collection = $name;
1082
        }
1083 1609
    }
1084
1085 40
    public function getBucketName() : ?string
1086
    {
1087 40
        return $this->bucketName;
1088
    }
1089
1090 1
    public function setBucketName(string $bucketName) : void
1091
    {
1092 1
        $this->bucketName = $bucketName;
1093 1
        $this->setCollection($bucketName . '.files');
1094 1
    }
1095
1096 10
    public function getChunkSizeBytes() : ?int
1097
    {
1098 10
        return $this->chunkSizeBytes;
1099
    }
1100
1101 122
    public function setChunkSizeBytes(int $chunkSizeBytes) : void
1102
    {
1103 122
        $this->chunkSizeBytes = $chunkSizeBytes;
1104 122
    }
1105
1106
    /**
1107
     * Get whether or not the documents collection is capped.
1108
     */
1109 11
    public function getCollectionCapped() : bool
1110
    {
1111 11
        return $this->collectionCapped;
1112
    }
1113
1114
    /**
1115
     * Set whether or not the documents collection is capped.
1116
     */
1117 1
    public function setCollectionCapped(bool $bool) : void
1118
    {
1119 1
        $this->collectionCapped = $bool;
1120 1
    }
1121
1122
    /**
1123
     * Get the collection size
1124
     */
1125 11
    public function getCollectionSize() : ?int
1126
    {
1127 11
        return $this->collectionSize;
1128
    }
1129
1130
    /**
1131
     * Set the collection size.
1132
     */
1133 1
    public function setCollectionSize(int $size) : void
1134
    {
1135 1
        $this->collectionSize = $size;
1136 1
    }
1137
1138
    /**
1139
     * Get the collection max.
1140
     */
1141 11
    public function getCollectionMax() : ?int
1142
    {
1143 11
        return $this->collectionMax;
1144
    }
1145
1146
    /**
1147
     * Set the collection max.
1148
     */
1149 1
    public function setCollectionMax(int $max) : void
1150
    {
1151 1
        $this->collectionMax = $max;
1152 1
    }
1153
1154
    /**
1155
     * Returns TRUE if this Document is mapped to a collection FALSE otherwise.
1156
     */
1157
    public function isMappedToCollection() : bool
1158
    {
1159
        return $this->collection ? true : false;
1160
    }
1161
1162
    /**
1163
     * Validates the storage strategy of a mapping for consistency
1164
     *
1165
     * @throws MappingException
1166
     */
1167 1511
    private function applyStorageStrategy(array &$mapping) : void
1168
    {
1169 1511
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1170 1491
            return;
1171
        }
1172
1173
        switch (true) {
1174 1476
            case $mapping['type'] === 'int':
1175 1475
            case $mapping['type'] === 'float':
1176 924
                $defaultStrategy   = self::STORAGE_STRATEGY_SET;
1177 924
                $allowedStrategies = [self::STORAGE_STRATEGY_SET, self::STORAGE_STRATEGY_INCREMENT];
1178 924
                break;
1179
1180 1475
            case $mapping['type'] === 'many':
1181 1171
                $defaultStrategy   = CollectionHelper::DEFAULT_STRATEGY;
1182
                $allowedStrategies = [
1183 1171
                    self::STORAGE_STRATEGY_PUSH_ALL,
1184 1171
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1185 1171
                    self::STORAGE_STRATEGY_SET,
1186 1171
                    self::STORAGE_STRATEGY_SET_ARRAY,
1187 1171
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1188 1171
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1189
                ];
1190 1171
                break;
1191
1192
            default:
1193 1463
                $defaultStrategy   = self::STORAGE_STRATEGY_SET;
1194 1463
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1195
        }
1196
1197 1476
        if (! isset($mapping['strategy'])) {
1198 1468
            $mapping['strategy'] = $defaultStrategy;
1199
        }
1200
1201 1476
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1202
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1203
        }
1204
1205 1476
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1206 1476
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1207 1
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1208
        }
1209 1475
    }
1210
1211
    /**
1212
     * Map a single embedded document.
1213
     */
1214 6
    public function mapOneEmbedded(array $mapping) : void
1215
    {
1216 6
        $mapping['embedded'] = true;
1217 6
        $mapping['type']     = 'one';
1218 6
        $this->mapField($mapping);
1219 5
    }
1220
1221
    /**
1222
     * Map a collection of embedded documents.
1223
     */
1224 5
    public function mapManyEmbedded(array $mapping) : void
1225
    {
1226 5
        $mapping['embedded'] = true;
1227 5
        $mapping['type']     = 'many';
1228 5
        $this->mapField($mapping);
1229 5
    }
1230
1231
    /**
1232
     * Map a single document reference.
1233
     */
1234 2
    public function mapOneReference(array $mapping) : void
1235
    {
1236 2
        $mapping['reference'] = true;
1237 2
        $mapping['type']      = 'one';
1238 2
        $this->mapField($mapping);
1239 2
    }
1240
1241
    /**
1242
     * Map a collection of document references.
1243
     */
1244 1
    public function mapManyReference(array $mapping) : void
1245
    {
1246 1
        $mapping['reference'] = true;
1247 1
        $mapping['type']      = 'many';
1248 1
        $this->mapField($mapping);
1249 1
    }
1250
1251
    /**
1252
     * INTERNAL:
1253
     * Adds a field mapping without completing/validating it.
1254
     * This is mainly used to add inherited field mappings to derived classes.
1255
     */
1256 182
    public function addInheritedFieldMapping(array $fieldMapping) : void
1257
    {
1258 182
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1259
1260 182
        if (! isset($fieldMapping['association'])) {
1261 182
            return;
1262
        }
1263
1264 132
        $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1265 132
    }
1266
1267
    /**
1268
     * INTERNAL:
1269
     * Adds an association mapping without completing/validating it.
1270
     * This is mainly used to add inherited association mappings to derived classes.
1271
     *
1272
     * @throws MappingException
1273
     */
1274 133
    public function addInheritedAssociationMapping(array $mapping) : void
1275
    {
1276 133
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1277 133
    }
1278
1279
    /**
1280
     * Checks whether the class has a mapped association with the given field name.
1281
     */
1282 31
    public function hasReference(string $fieldName) : bool
1283
    {
1284 31
        return isset($this->fieldMappings[$fieldName]['reference']);
1285
    }
1286
1287
    /**
1288
     * Checks whether the class has a mapped embed with the given field name.
1289
     */
1290 4
    public function hasEmbed(string $fieldName) : bool
1291
    {
1292 4
        return isset($this->fieldMappings[$fieldName]['embedded']);
1293
    }
1294
1295
    /**
1296
     * {@inheritDoc}
1297
     *
1298
     * Checks whether the class has a mapped association (embed or reference) with the given field name.
1299
     */
1300 6
    public function hasAssociation($fieldName) : bool
1301
    {
1302 6
        return $this->hasReference($fieldName) || $this->hasEmbed($fieldName);
1303
    }
1304
1305
    /**
1306
     * {@inheritDoc}
1307
     *
1308
     * Checks whether the class has a mapped reference or embed for the specified field and
1309
     * is a single valued association.
1310
     */
1311
    public function isSingleValuedAssociation($fieldName) : bool
1312
    {
1313
        return $this->isSingleValuedReference($fieldName) || $this->isSingleValuedEmbed($fieldName);
1314
    }
1315
1316
    /**
1317
     * {@inheritDoc}
1318
     *
1319
     * Checks whether the class has a mapped reference or embed for the specified field and
1320
     * is a collection valued association.
1321
     */
1322
    public function isCollectionValuedAssociation($fieldName) : bool
1323
    {
1324
        return $this->isCollectionValuedReference($fieldName) || $this->isCollectionValuedEmbed($fieldName);
1325
    }
1326
1327
    /**
1328
     * Checks whether the class has a mapped association for the specified field
1329
     * and if yes, checks whether it is a single-valued association (to-one).
1330
     */
1331
    public function isSingleValuedReference(string $fieldName) : bool
1332
    {
1333
        return isset($this->fieldMappings[$fieldName]['association']) &&
1334
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_ONE;
1335
    }
1336
1337
    /**
1338
     * Checks whether the class has a mapped association for the specified field
1339
     * and if yes, checks whether it is a collection-valued association (to-many).
1340
     */
1341
    public function isCollectionValuedReference(string $fieldName) : bool
1342
    {
1343
        return isset($this->fieldMappings[$fieldName]['association']) &&
1344
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_MANY;
1345
    }
1346
1347
    /**
1348
     * Checks whether the class has a mapped embedded document for the specified field
1349
     * and if yes, checks whether it is a single-valued association (to-one).
1350
     */
1351
    public function isSingleValuedEmbed(string $fieldName) : bool
1352
    {
1353
        return isset($this->fieldMappings[$fieldName]['association']) &&
1354
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_ONE;
1355
    }
1356
1357
    /**
1358
     * Checks whether the class has a mapped embedded document for the specified field
1359
     * and if yes, checks whether it is a collection-valued association (to-many).
1360
     */
1361
    public function isCollectionValuedEmbed(string $fieldName) : bool
1362
    {
1363
        return isset($this->fieldMappings[$fieldName]['association']) &&
1364
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_MANY;
1365
    }
1366
1367
    /**
1368
     * Sets the ID generator used to generate IDs for instances of this class.
1369
     */
1370 1426
    public function setIdGenerator(AbstractIdGenerator $generator) : void
1371
    {
1372 1426
        $this->idGenerator = $generator;
1373 1426
    }
1374
1375
    /**
1376
     * Casts the identifier to its portable PHP type.
1377
     *
1378
     * @param mixed $id
1379
     *
1380
     * @return mixed $id
1381
     */
1382 644
    public function getPHPIdentifierValue($id)
1383
    {
1384 644
        $idType = $this->fieldMappings[$this->identifier]['type'];
1385 644
        return Type::getType($idType)->convertToPHPValue($id);
1386
    }
1387
1388
    /**
1389
     * Casts the identifier to its database type.
1390
     *
1391
     * @param mixed $id
1392
     *
1393
     * @return mixed $id
1394
     */
1395 708
    public function getDatabaseIdentifierValue($id)
1396
    {
1397 708
        $idType = $this->fieldMappings[$this->identifier]['type'];
1398 708
        return Type::getType($idType)->convertToDatabaseValue($id);
1399
    }
1400
1401
    /**
1402
     * Sets the document identifier of a document.
1403
     *
1404
     * The value will be converted to a PHP type before being set.
1405
     *
1406
     * @param mixed $id
1407
     */
1408 573
    public function setIdentifierValue(object $document, $id) : void
1409
    {
1410 573
        $id = $this->getPHPIdentifierValue($id);
1411 573
        $this->reflFields[$this->identifier]->setValue($document, $id);
1412 573
    }
1413
1414
    /**
1415
     * Gets the document identifier as a PHP type.
1416
     *
1417
     * @return mixed $id
1418
     */
1419 651
    public function getIdentifierValue(object $document)
1420
    {
1421 651
        return $this->reflFields[$this->identifier]->getValue($document);
1422
    }
1423
1424
    /**
1425
     * {@inheritDoc}
1426
     *
1427
     * Since MongoDB only allows exactly one identifier field this is a proxy
1428
     * to {@see getIdentifierValue()} and returns an array with the identifier
1429
     * field as a key.
1430
     */
1431
    public function getIdentifierValues($object) : array
1432
    {
1433
        return [$this->identifier => $this->getIdentifierValue($object)];
1434
    }
1435
1436
    /**
1437
     * Get the document identifier object as a database type.
1438
     *
1439
     * @return mixed $id
1440
     */
1441 30
    public function getIdentifierObject(object $document)
1442
    {
1443 30
        return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));
1444
    }
1445
1446
    /**
1447
     * Sets the specified field to the specified value on the given document.
1448
     *
1449
     * @param mixed $value
1450
     */
1451 8
    public function setFieldValue(object $document, string $field, $value) : void
1452
    {
1453 8
        if ($document instanceof GhostObjectInterface && ! $document->isProxyInitialized()) {
0 ignored issues
show
The class ProxyManager\Proxy\GhostObjectInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
1454
            //property changes to an uninitialized proxy will not be tracked or persisted,
1455
            //so the proxy needs to be loaded first.
1456 1
            $document->initializeProxy();
1457
        }
1458
1459 8
        $this->reflFields[$field]->setValue($document, $value);
1460 8
    }
1461
1462
    /**
1463
     * Gets the specified field's value off the given document.
1464
     *
1465
     * @return mixed
1466
     */
1467 32
    public function getFieldValue(object $document, string $field)
1468
    {
1469 32
        if ($document instanceof GhostObjectInterface && $field !== $this->identifier && ! $document->isProxyInitialized()) {
0 ignored issues
show
The class ProxyManager\Proxy\GhostObjectInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
1470 1
            $document->initializeProxy();
1471
        }
1472
1473 32
        return $this->reflFields[$field]->getValue($document);
1474
    }
1475
1476
    /**
1477
     * Gets the mapping of a field.
1478
     *
1479
     * @throws MappingException If the $fieldName is not found in the fieldMappings array.
1480
     */
1481 190
    public function getFieldMapping(string $fieldName) : array
1482
    {
1483 190
        if (! isset($this->fieldMappings[$fieldName])) {
1484 6
            throw MappingException::mappingNotFound($this->name, $fieldName);
1485
        }
1486 188
        return $this->fieldMappings[$fieldName];
1487
    }
1488
1489
    /**
1490
     * Gets mappings of fields holding embedded document(s).
1491
     */
1492 593
    public function getEmbeddedFieldsMappings() : array
1493
    {
1494 593
        return array_filter(
1495 593
            $this->associationMappings,
1496
            static function ($assoc) {
1497 455
                return ! empty($assoc['embedded']);
1498 593
            }
1499
        );
1500
    }
1501
1502
    /**
1503
     * Gets the field mapping by its DB name.
1504
     * E.g. it returns identifier's mapping when called with _id.
1505
     *
1506
     * @throws MappingException
1507
     */
1508 14
    public function getFieldMappingByDbFieldName(string $dbFieldName) : array
1509
    {
1510 14
        foreach ($this->fieldMappings as $mapping) {
1511 14
            if ($mapping['name'] === $dbFieldName) {
1512 13
                return $mapping;
1513
            }
1514
        }
1515
1516 1
        throw MappingException::mappingNotFoundByDbName($this->name, $dbFieldName);
1517
    }
1518
1519
    /**
1520
     * Check if the field is not null.
1521
     */
1522 1
    public function isNullable(string $fieldName) : bool
1523
    {
1524 1
        $mapping = $this->getFieldMapping($fieldName);
1525 1
        return isset($mapping['nullable']) && $mapping['nullable'] === true;
1526
    }
1527
1528
    /**
1529
     * Checks whether the document has a discriminator field and value configured.
1530
     */
1531 519
    public function hasDiscriminator() : bool
1532
    {
1533 519
        return isset($this->discriminatorField, $this->discriminatorValue);
1534
    }
1535
1536
    /**
1537
     * Sets the type of Id generator to use for the mapped class.
1538
     */
1539 958
    public function setIdGeneratorType(int $generatorType) : void
1540
    {
1541 958
        $this->generatorType = $generatorType;
1542 958
    }
1543
1544
    /**
1545
     * Sets the Id generator options.
1546
     */
1547
    public function setIdGeneratorOptions(array $generatorOptions) : void
1548
    {
1549
        $this->generatorOptions = $generatorOptions;
1550
    }
1551
1552 612
    public function isInheritanceTypeNone() : bool
1553
    {
1554 612
        return $this->inheritanceType === self::INHERITANCE_TYPE_NONE;
1555
    }
1556
1557
    /**
1558
     * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.
1559
     */
1560 957
    public function isInheritanceTypeSingleCollection() : bool
1561
    {
1562 957
        return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION;
1563
    }
1564
1565
    /**
1566
     * Checks whether the mapped class uses the COLLECTION_PER_CLASS inheritance mapping strategy.
1567
     */
1568
    public function isInheritanceTypeCollectionPerClass() : bool
1569
    {
1570
        return $this->inheritanceType === self::INHERITANCE_TYPE_COLLECTION_PER_CLASS;
1571
    }
1572
1573
    /**
1574
     * Sets the mapped subclasses of this class.
1575
     *
1576
     * @param string[] $subclasses The names of all mapped subclasses.
1577
     */
1578 2
    public function setSubclasses(array $subclasses) : void
1579
    {
1580 2
        foreach ($subclasses as $subclass) {
1581 2
            $this->subClasses[] = $subclass;
1582
        }
1583 2
    }
1584
1585
    /**
1586
     * Sets the parent class names.
1587
     * Assumes that the class names in the passed array are in the order:
1588
     * directParent -> directParentParent -> directParentParentParent ... -> root.
1589
     *
1590
     * @param string[] $classNames
1591
     */
1592 1481
    public function setParentClasses(array $classNames) : void
1593
    {
1594 1481
        $this->parentClasses = $classNames;
1595
1596 1481
        if (count($classNames) <= 0) {
1597 1480
            return;
1598
        }
1599
1600 166
        $this->rootDocumentName = (string) array_pop($classNames);
1601 166
    }
1602
1603
    /**
1604
     * Checks whether the class will generate a new \MongoDB\BSON\ObjectId instance for us.
1605
     */
1606
    public function isIdGeneratorAuto() : bool
1607
    {
1608
        return $this->generatorType === self::GENERATOR_TYPE_AUTO;
1609
    }
1610
1611
    /**
1612
     * Checks whether the class will use a collection to generate incremented identifiers.
1613
     */
1614
    public function isIdGeneratorIncrement() : bool
1615
    {
1616
        return $this->generatorType === self::GENERATOR_TYPE_INCREMENT;
1617
    }
1618
1619
    /**
1620
     * Checks whether the class will generate a uuid id.
1621
     */
1622
    public function isIdGeneratorUuid() : bool
1623
    {
1624
        return $this->generatorType === self::GENERATOR_TYPE_UUID;
1625
    }
1626
1627
    /**
1628
     * Checks whether the class uses no id generator.
1629
     */
1630
    public function isIdGeneratorNone() : bool
1631
    {
1632
        return $this->generatorType === self::GENERATOR_TYPE_NONE;
1633
    }
1634
1635
    /**
1636
     * Sets the version field mapping used for versioning. Sets the default
1637
     * value to use depending on the column type.
1638
     *
1639
     * @throws LockException
1640
     */
1641 151
    public function setVersionMapping(array &$mapping) : void
1642
    {
1643 151
        if ($mapping['type'] !== 'int' && $mapping['type'] !== 'date') {
1644 1
            throw LockException::invalidVersionFieldType($mapping['type']);
1645
        }
1646
1647 150
        $this->isVersioned  = true;
1648 150
        $this->versionField = $mapping['fieldName'];
1649 150
    }
1650
1651
    /**
1652
     * Sets whether this class is to be versioned for optimistic locking.
1653
     */
1654 958
    public function setVersioned(bool $bool) : void
1655
    {
1656 958
        $this->isVersioned = $bool;
1657 958
    }
1658
1659
    /**
1660
     * Sets the name of the field that is to be used for versioning if this class is
1661
     * versioned for optimistic locking.
1662
     */
1663 958
    public function setVersionField(?string $versionField) : void
1664
    {
1665 958
        $this->versionField = $versionField;
1666 958
    }
1667
1668
    /**
1669
     * Sets the version field mapping used for versioning. Sets the default
1670
     * value to use depending on the column type.
1671
     *
1672
     * @throws LockException
1673
     */
1674 22
    public function setLockMapping(array &$mapping) : void
1675
    {
1676 22
        if ($mapping['type'] !== 'int') {
1677 1
            throw LockException::invalidLockFieldType($mapping['type']);
1678
        }
1679
1680 21
        $this->isLockable = true;
1681 21
        $this->lockField  = $mapping['fieldName'];
1682 21
    }
1683
1684
    /**
1685
     * Sets whether this class is to allow pessimistic locking.
1686
     */
1687
    public function setLockable(bool $bool) : void
1688
    {
1689
        $this->isLockable = $bool;
1690
    }
1691
1692
    /**
1693
     * Sets the name of the field that is to be used for storing whether a document
1694
     * is currently locked or not.
1695
     */
1696
    public function setLockField(string $lockField) : void
1697
    {
1698
        $this->lockField = $lockField;
1699
    }
1700
1701
    /**
1702
     * Marks this class as read only, no change tracking is applied to it.
1703
     */
1704 5
    public function markReadOnly() : void
1705
    {
1706 5
        $this->isReadOnly = true;
1707 5
    }
1708
1709
    /**
1710
     * {@inheritDoc}
1711
     */
1712
    public function getFieldNames() : array
1713
    {
1714
        return array_keys($this->fieldMappings);
1715
    }
1716
1717
    /**
1718
     * {@inheritDoc}
1719
     */
1720
    public function getAssociationNames() : array
1721
    {
1722
        return array_keys($this->associationMappings);
1723
    }
1724
1725
    /**
1726
     * {@inheritDoc}
1727
     */
1728
    public function getTypeOfField($fieldName) : ?string
1729
    {
1730
        return isset($this->fieldMappings[$fieldName]) ?
1731
            $this->fieldMappings[$fieldName]['type'] : null;
1732
    }
1733
1734
    /**
1735
     * {@inheritDoc}
1736
     */
1737 4
    public function getAssociationTargetClass($assocName) : string
1738
    {
1739 4
        if (! isset($this->associationMappings[$assocName])) {
1740 2
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1741
        }
1742
1743 2
        return $this->associationMappings[$assocName]['targetDocument'];
1744
    }
1745
1746
    /**
1747
     * Retrieve the collectionClass associated with an association
1748
     */
1749
    public function getAssociationCollectionClass(string $assocName) : string
1750
    {
1751
        if (! isset($this->associationMappings[$assocName])) {
1752
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1753
        }
1754
1755
        if (! array_key_exists('collectionClass', $this->associationMappings[$assocName])) {
1756
            throw new InvalidArgumentException("collectionClass can only be applied to 'embedMany' and 'referenceMany' associations.");
1757
        }
1758
1759
        return $this->associationMappings[$assocName]['collectionClass'];
1760
    }
1761
1762
    /**
1763
     * {@inheritDoc}
1764
     */
1765
    public function isAssociationInverseSide($fieldName) : bool
1766
    {
1767
        throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1768
    }
1769
1770
    /**
1771
     * {@inheritDoc}
1772
     */
1773
    public function getAssociationMappedByTargetField($fieldName)
1774
    {
1775
        throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1776
    }
1777
1778
    /**
1779
     * Map a field.
1780
     *
1781
     * @throws MappingException
1782
     */
1783 1527
    public function mapField(array $mapping) : array
1784
    {
1785 1527
        if (! isset($mapping['fieldName']) && isset($mapping['name'])) {
1786 5
            $mapping['fieldName'] = $mapping['name'];
1787
        }
1788 1527
        if (! isset($mapping['fieldName']) || ! is_string($mapping['fieldName'])) {
1789
            throw MappingException::missingFieldName($this->name);
1790
        }
1791 1527
        if (! isset($mapping['name'])) {
1792 1526
            $mapping['name'] = $mapping['fieldName'];
1793
        }
1794
1795 1527
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1796 1
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, (string) $mapping['name']);
1797
        }
1798 1526
        if ($this->discriminatorField !== null && $this->discriminatorField === $mapping['name']) {
1799 1
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1800
        }
1801 1525
        if (isset($mapping['collectionClass'])) {
1802 115
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1803
        }
1804 1525
        if (! empty($mapping['collectionClass'])) {
1805 115
            $rColl = new ReflectionClass($mapping['collectionClass']);
1806 115
            if (! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1807 1
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1808
            }
1809
        }
1810
1811 1524
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1812 1
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1813
        }
1814
1815 1523
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : [];
1816
1817 1523
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
1818 1215
            $cascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1819
        }
1820
1821 1523
        if (isset($mapping['embedded'])) {
1822 1176
            unset($mapping['cascade']);
1823 1518
        } elseif (isset($mapping['cascade'])) {
1824 984
            $mapping['cascade'] = $cascades;
1825
        }
1826
1827 1523
        $mapping['isCascadeRemove']  = in_array('remove', $cascades);
1828 1523
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1829 1523
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1830 1523
        $mapping['isCascadeMerge']   = in_array('merge', $cascades);
1831 1523
        $mapping['isCascadeDetach']  = in_array('detach', $cascades);
1832
1833 1523
        if (isset($mapping['id']) && $mapping['id'] === true) {
1834 1489
            $mapping['name']  = '_id';
1835 1489
            $this->identifier = $mapping['fieldName'];
1836 1489
            if (isset($mapping['strategy'])) {
1837 1483
                $this->generatorType = constant(self::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
1838
            }
1839 1489
            $this->generatorOptions = $mapping['options'] ?? [];
1840 1489
            switch ($this->generatorType) {
1841 1489
                case self::GENERATOR_TYPE_AUTO:
1842 1417
                    $mapping['type'] = 'id';
1843 1417
                    break;
1844
                default:
1845 208
                    if (! empty($this->generatorOptions['type'])) {
1846 56
                        $mapping['type'] = $this->generatorOptions['type'];
1847 152
                    } elseif (empty($mapping['type'])) {
1848 140
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
1849
                    }
1850
            }
1851 1489
            unset($this->generatorOptions['type']);
1852
        }
1853
1854 1523
        if (! isset($mapping['nullable'])) {
1855 40
            $mapping['nullable'] = false;
1856
        }
1857
1858 1523
        if (isset($mapping['reference'])
1859 1523
            && isset($mapping['storeAs'])
1860 1523
            && $mapping['storeAs'] === self::REFERENCE_STORE_AS_ID
1861 1523
            && ! isset($mapping['targetDocument'])
1862
        ) {
1863 3
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
1864
        }
1865
1866 1520
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
1867 1520
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
1868 4
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
1869
        }
1870
1871 1516
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
1872 1
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
1873
        }
1874
1875 1515
        if (isset($mapping['repositoryMethod']) && ! (empty($mapping['skip']) && empty($mapping['limit']) && empty($mapping['sort']))) {
1876 3
            throw MappingException::repositoryMethodCanNotBeCombinedWithSkipLimitAndSort($this->name, $mapping['fieldName']);
1877
        }
1878
1879 1512
        if (isset($mapping['reference']) && $mapping['type'] === 'one') {
1880 1091
            $mapping['association'] = self::REFERENCE_ONE;
1881
        }
1882 1512
        if (isset($mapping['reference']) && $mapping['type'] === 'many') {
1883 1035
            $mapping['association'] = self::REFERENCE_MANY;
1884
        }
1885 1512
        if (isset($mapping['embedded']) && $mapping['type'] === 'one') {
1886 1031
            $mapping['association'] = self::EMBED_ONE;
1887
        }
1888 1512
        if (isset($mapping['embedded']) && $mapping['type'] === 'many') {
1889 1072
            $mapping['association'] = self::EMBED_MANY;
1890
        }
1891
1892 1512
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
1893 187
            $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
1894
        }
1895
1896 1512
        if (isset($mapping['version'])) {
1897 151
            $mapping['notSaved'] = true;
1898 151
            $this->setVersionMapping($mapping);
1899
        }
1900 1512
        if (isset($mapping['lock'])) {
1901 22
            $mapping['notSaved'] = true;
1902 22
            $this->setLockMapping($mapping);
1903
        }
1904 1512
        $mapping['isOwningSide']  = true;
1905 1512
        $mapping['isInverseSide'] = false;
1906 1512
        if (isset($mapping['reference'])) {
1907 1158
            if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
1908 147
                $mapping['isOwningSide']  = true;
1909 147
                $mapping['isInverseSide'] = false;
1910
            }
1911 1158
            if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
1912 883
                $mapping['isInverseSide'] = true;
1913 883
                $mapping['isOwningSide']  = false;
1914
            }
1915 1158
            if (isset($mapping['repositoryMethod'])) {
1916 121
                $mapping['isInverseSide'] = true;
1917 121
                $mapping['isOwningSide']  = false;
1918
            }
1919 1158
            if (! isset($mapping['orphanRemoval'])) {
1920 1139
                $mapping['orphanRemoval'] = false;
1921
            }
1922
        }
1923
1924 1512
        if (! empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || ! $mapping['isInverseSide'])) {
1925
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
1926
        }
1927
1928 1512
        if ($this->isFile && ! $this->isAllowedGridFSField($mapping['name'])) {
1929 1
            throw MappingException::fieldNotAllowedForGridFS($this->name, $mapping['fieldName']);
1930
        }
1931
1932 1511
        $this->applyStorageStrategy($mapping);
1933 1510
        $this->checkDuplicateMapping($mapping);
1934
1935 1510
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
1936 1510
        if (isset($mapping['association'])) {
1937 1313
            $this->associationMappings[$mapping['fieldName']] = $mapping;
1938
        }
1939
1940 1510
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
1941 1509
        $reflProp->setAccessible(true);
1942 1509
        $this->reflFields[$mapping['fieldName']] = $reflProp;
1943
1944 1509
        return $mapping;
1945
    }
1946
1947
    /**
1948
     * Determines which fields get serialized.
1949
     *
1950
     * It is only serialized what is necessary for best unserialization performance.
1951
     * That means any metadata properties that are not set or empty or simply have
1952
     * their default value are NOT serialized.
1953
     *
1954
     * Parts that are also NOT serialized because they can not be properly unserialized:
1955
     *      - reflClass (ReflectionClass)
1956
     *      - reflFields (ReflectionProperty array)
1957
     *
1958
     * @return array The names of all the fields that should be serialized.
1959
     */
1960 6
    public function __sleep()
1961
    {
1962
        // This metadata is always serialized/cached.
1963
        $serialized = [
1964 6
            'fieldMappings',
1965
            'associationMappings',
1966
            'identifier',
1967
            'name',
1968
            'db',
1969
            'collection',
1970
            'readPreference',
1971
            'readPreferenceTags',
1972
            'writeConcern',
1973
            'rootDocumentName',
1974
            'generatorType',
1975
            'generatorOptions',
1976
            'idGenerator',
1977
            'indexes',
1978
            'shardKey',
1979
        ];
1980
1981
        // The rest of the metadata is only serialized if necessary.
1982 6
        if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {
1983
            $serialized[] = 'changeTrackingPolicy';
1984
        }
1985
1986 6
        if ($this->customRepositoryClassName) {
1987 1
            $serialized[] = 'customRepositoryClassName';
1988
        }
1989
1990 6
        if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE || $this->discriminatorField !== null) {
1991 4
            $serialized[] = 'inheritanceType';
1992 4
            $serialized[] = 'discriminatorField';
1993 4
            $serialized[] = 'discriminatorValue';
1994 4
            $serialized[] = 'discriminatorMap';
1995 4
            $serialized[] = 'defaultDiscriminatorValue';
1996 4
            $serialized[] = 'parentClasses';
1997 4
            $serialized[] = 'subClasses';
1998
        }
1999
2000 6
        if ($this->isMappedSuperclass) {
2001 1
            $serialized[] = 'isMappedSuperclass';
2002
        }
2003
2004 6
        if ($this->isEmbeddedDocument) {
2005 1
            $serialized[] = 'isEmbeddedDocument';
2006
        }
2007
2008 6
        if ($this->isQueryResultDocument) {
2009
            $serialized[] = 'isQueryResultDocument';
2010
        }
2011
2012 6
        if ($this->isFile) {
2013
            $serialized[] = 'isFile';
2014
            $serialized[] = 'bucketName';
2015
            $serialized[] = 'chunkSizeBytes';
2016
        }
2017
2018 6
        if ($this->isVersioned) {
2019
            $serialized[] = 'isVersioned';
2020
            $serialized[] = 'versionField';
2021
        }
2022
2023 6
        if ($this->lifecycleCallbacks) {
2024
            $serialized[] = 'lifecycleCallbacks';
2025
        }
2026
2027 6
        if ($this->collectionCapped) {
2028 1
            $serialized[] = 'collectionCapped';
2029 1
            $serialized[] = 'collectionSize';
2030 1
            $serialized[] = 'collectionMax';
2031
        }
2032
2033 6
        if ($this->isReadOnly) {
2034
            $serialized[] = 'isReadOnly';
2035
        }
2036
2037 6
        return $serialized;
2038
    }
2039
2040
    /**
2041
     * Restores some state that can not be serialized/unserialized.
2042
     */
2043 6
    public function __wakeup()
2044
    {
2045
        // Restore ReflectionClass and properties
2046 6
        $this->reflClass    = new ReflectionClass($this->name);
2047 6
        $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...\InstantiatorInterface> 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...
2048
2049 6
        foreach ($this->fieldMappings as $field => $mapping) {
2050 3
            if (isset($mapping['declared'])) {
2051 1
                $reflField = new ReflectionProperty($mapping['declared'], $field);
2052
            } else {
2053 3
                $reflField = $this->reflClass->getProperty($field);
2054
            }
2055 3
            $reflField->setAccessible(true);
2056 3
            $this->reflFields[$field] = $reflField;
2057
        }
2058 6
    }
2059
2060
    /**
2061
     * Creates a new instance of the mapped class, without invoking the constructor.
2062
     */
2063 367
    public function newInstance() : object
2064
    {
2065 367
        return $this->instantiator->instantiate($this->name);
2066
    }
2067
2068 124
    private function isAllowedGridFSField(string $name) : bool
2069
    {
2070 124
        return in_array($name, self::ALLOWED_GRIDFS_FIELDS, true);
2071
    }
2072
2073 1510
    private function checkDuplicateMapping(array $mapping) : void
2074
    {
2075 1510
        if ($mapping['notSaved'] ?? false) {
2076 912
            return;
2077
        }
2078
2079 1510
        foreach ($this->fieldMappings as $fieldName => $otherMapping) {
2080
            // Ignore fields with the same name - we can safely override their mapping
2081 1464
            if ($mapping['fieldName'] === $fieldName) {
2082 115
                continue;
2083
            }
2084
2085
            // Ignore fields with a different name in the database
2086 1460
            if ($mapping['name'] !== $otherMapping['name']) {
2087 1460
                continue;
2088
            }
2089
2090
            // If the other field is not saved, ignore it as well
2091 2
            if ($otherMapping['notSaved'] ?? false) {
2092
                continue;
2093
            }
2094
2095 2
            throw MappingException::duplicateDatabaseFieldName($this->getName(), $mapping['fieldName'], $mapping['name'], $fieldName);
0 ignored issues
show
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
2096
        }
2097 1510
    }
2098
}
2099