Completed
Pull Request — master (#2183)
by Maciej
20:02
created

ClassMetadata::isView()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
1679 1
            throw LockException::invalidVersionFieldType($mapping['type']);
1680
        }
1681
1682 174
        $this->isVersioned  = true;
1683 174
        $this->versionField = $mapping['fieldName'];
1684 174
    }
1685
1686
    /**
1687
     * Sets whether this class is to be versioned for optimistic locking.
1688
     */
1689 1037
    public function setVersioned(bool $bool) : void
1690
    {
1691 1037
        $this->isVersioned = $bool;
1692 1037
    }
1693
1694
    /**
1695
     * Sets the name of the field that is to be used for versioning if this class is
1696
     * versioned for optimistic locking.
1697
     */
1698 1037
    public function setVersionField(?string $versionField) : void
1699
    {
1700 1037
        $this->versionField = $versionField;
1701 1037
    }
1702
1703
    /**
1704
     * Sets the version field mapping used for versioning. Sets the default
1705
     * value to use depending on the column type.
1706
     *
1707
     * @throws LockException
1708
     */
1709 28
    public function setLockMapping(array &$mapping) : void
1710
    {
1711 28
        if ($mapping['type'] !== 'int') {
1712 1
            throw LockException::invalidLockFieldType($mapping['type']);
1713
        }
1714
1715 27
        $this->isLockable = true;
1716 27
        $this->lockField  = $mapping['fieldName'];
1717 27
    }
1718
1719
    /**
1720
     * Sets whether this class is to allow pessimistic locking.
1721
     */
1722
    public function setLockable(bool $bool) : void
1723
    {
1724
        $this->isLockable = $bool;
1725
    }
1726
1727
    /**
1728
     * Sets the name of the field that is to be used for storing whether a document
1729
     * is currently locked or not.
1730
     */
1731
    public function setLockField(string $lockField) : void
1732
    {
1733
        $this->lockField = $lockField;
1734
    }
1735
1736
    /**
1737
     * Marks this class as read only, no change tracking is applied to it.
1738
     */
1739 5
    public function markReadOnly() : void
1740
    {
1741 5
        $this->isReadOnly = true;
1742 5
    }
1743
1744 11
    public function getRootClass() : ?string
1745
    {
1746 11
        return $this->rootClass;
1747
    }
1748
1749 1594
    public function isView() : bool
1750
    {
1751 1594
        return $this->isView;
1752
    }
1753
1754 133
    public function markViewOf(string $rootClass) : void
1755
    {
1756 133
        $this->isView    = true;
1757 133
        $this->rootClass = $rootClass;
1758 133
    }
1759
1760
    /**
1761
     * {@inheritDoc}
1762
     */
1763
    public function getFieldNames() : array
1764
    {
1765
        return array_keys($this->fieldMappings);
1766
    }
1767
1768
    /**
1769
     * {@inheritDoc}
1770
     */
1771
    public function getAssociationNames() : array
1772
    {
1773
        return array_keys($this->associationMappings);
1774
    }
1775
1776
    /**
1777
     * {@inheritDoc}
1778
     */
1779
    public function getTypeOfField($fieldName) : ?string
1780
    {
1781
        return isset($this->fieldMappings[$fieldName]) ?
1782
            $this->fieldMappings[$fieldName]['type'] : null;
1783
    }
1784
1785
    /**
1786
     * {@inheritDoc}
1787
     */
1788 5
    public function getAssociationTargetClass($assocName) : ?string
1789
    {
1790 5
        if (! isset($this->associationMappings[$assocName])) {
1791 2
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1792
        }
1793
1794 3
        return $this->associationMappings[$assocName]['targetDocument'];
1795
    }
1796
1797
    /**
1798
     * Retrieve the collectionClass associated with an association
1799
     */
1800
    public function getAssociationCollectionClass(string $assocName) : string
1801
    {
1802
        if (! isset($this->associationMappings[$assocName])) {
1803
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1804
        }
1805
1806
        if (! array_key_exists('collectionClass', $this->associationMappings[$assocName])) {
1807
            throw new InvalidArgumentException("collectionClass can only be applied to 'embedMany' and 'referenceMany' associations.");
1808
        }
1809
1810
        return $this->associationMappings[$assocName]['collectionClass'];
1811
    }
1812
1813
    /**
1814
     * {@inheritDoc}
1815
     */
1816
    public function isAssociationInverseSide($fieldName) : bool
1817
    {
1818
        throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1819
    }
1820
1821
    /**
1822
     * {@inheritDoc}
1823
     */
1824
    public function getAssociationMappedByTargetField($fieldName)
1825
    {
1826
        throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1827
    }
1828
1829
    /**
1830
     * Map a field.
1831
     *
1832
     * @throws MappingException
1833
     */
1834 1638
    public function mapField(array $mapping) : array
1835
    {
1836 1638
        if (! isset($mapping['fieldName']) && isset($mapping['name'])) {
1837 9
            $mapping['fieldName'] = $mapping['name'];
1838
        }
1839 1638
        if (! isset($mapping['fieldName']) || ! is_string($mapping['fieldName'])) {
1840
            throw MappingException::missingFieldName($this->name);
1841
        }
1842 1638
        if (! isset($mapping['name'])) {
1843 1637
            $mapping['name'] = $mapping['fieldName'];
1844
        }
1845
1846 1638
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1847 1
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, (string) $mapping['name']);
1848
        }
1849 1637
        if ($this->discriminatorField !== null && $this->discriminatorField === $mapping['name']) {
1850 1
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1851
        }
1852 1636
        if (isset($mapping['collectionClass'])) {
1853 134
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1854
        }
1855 1636
        if (! empty($mapping['collectionClass'])) {
1856 134
            $rColl = new ReflectionClass($mapping['collectionClass']);
1857 134
            if (! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1858 1
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1859
            }
1860
        }
1861
1862 1635
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1863 1
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1864
        }
1865
1866 1634
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : [];
1867
1868 1634
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
1869 1311
            $cascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1870
        }
1871
1872 1634
        if (isset($mapping['embedded'])) {
1873 1262
            unset($mapping['cascade']);
1874 1628
        } elseif (isset($mapping['cascade'])) {
1875 1056
            $mapping['cascade'] = $cascades;
1876
        }
1877
1878 1634
        $mapping['isCascadeRemove']  = in_array('remove', $cascades);
1879 1634
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1880 1634
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1881 1634
        $mapping['isCascadeMerge']   = in_array('merge', $cascades);
1882 1634
        $mapping['isCascadeDetach']  = in_array('detach', $cascades);
1883
1884 1634
        if (isset($mapping['id']) && $mapping['id'] === true) {
1885 1601
            $mapping['name']  = '_id';
1886 1601
            $this->identifier = $mapping['fieldName'];
1887 1601
            if (isset($mapping['strategy'])) {
1888 1589
                $this->generatorType = constant(self::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
1889
            }
1890 1601
            $this->generatorOptions = $mapping['options'] ?? [];
1891 1601
            switch ($this->generatorType) {
1892 1601
                case self::GENERATOR_TYPE_AUTO:
1893 1531
                    $mapping['type'] = 'id';
1894 1531
                    break;
1895
                default:
1896 230
                    if (! empty($this->generatorOptions['type'])) {
1897 56
                        $mapping['type'] = $this->generatorOptions['type'];
1898 174
                    } elseif (empty($mapping['type'])) {
1899 158
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
1900
                    }
1901
            }
1902 1601
            unset($this->generatorOptions['type']);
1903
        }
1904
1905 1634
        if (! isset($mapping['nullable'])) {
1906 45
            $mapping['nullable'] = false;
1907
        }
1908
1909 1634
        if (isset($mapping['reference'])
1910 1634
            && isset($mapping['storeAs'])
1911 1634
            && $mapping['storeAs'] === self::REFERENCE_STORE_AS_ID
1912 1634
            && ! isset($mapping['targetDocument'])
1913
        ) {
1914 3
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
1915
        }
1916
1917 1631
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
1918 1631
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
1919 4
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
1920
        }
1921
1922 1627
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
1923 1
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
1924
        }
1925
1926 1626
        if (isset($mapping['repositoryMethod']) && ! (empty($mapping['skip']) && empty($mapping['limit']) && empty($mapping['sort']))) {
1927 3
            throw MappingException::repositoryMethodCanNotBeCombinedWithSkipLimitAndSort($this->name, $mapping['fieldName']);
1928
        }
1929
1930 1623
        if (isset($mapping['reference']) && $mapping['type'] === 'one') {
1931 1179
            $mapping['association'] = self::REFERENCE_ONE;
1932
        }
1933 1623
        if (isset($mapping['reference']) && $mapping['type'] === 'many') {
1934 1112
            $mapping['association'] = self::REFERENCE_MANY;
1935
        }
1936 1623
        if (isset($mapping['embedded']) && $mapping['type'] === 'one') {
1937 1110
            $mapping['association'] = self::EMBED_ONE;
1938
        }
1939 1623
        if (isset($mapping['embedded']) && $mapping['type'] === 'many') {
1940 1151
            $mapping['association'] = self::EMBED_MANY;
1941
        }
1942
1943 1623
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
1944 970
            $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
1945
        }
1946
1947 1623
        if (isset($mapping['version'])) {
1948 175
            $mapping['notSaved'] = true;
1949 175
            $this->setVersionMapping($mapping);
1950
        }
1951 1623
        if (isset($mapping['lock'])) {
1952 28
            $mapping['notSaved'] = true;
1953 28
            $this->setLockMapping($mapping);
1954
        }
1955 1623
        $mapping['isOwningSide']  = true;
1956 1623
        $mapping['isInverseSide'] = false;
1957 1623
        if (isset($mapping['reference'])) {
1958 1246
            if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
1959 273
                $mapping['isOwningSide']  = true;
1960 273
                $mapping['isInverseSide'] = false;
1961
            }
1962 1246
            if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
1963 952
                $mapping['isInverseSide'] = true;
1964 952
                $mapping['isOwningSide']  = false;
1965
            }
1966 1246
            if (isset($mapping['repositoryMethod'])) {
1967 141
                $mapping['isInverseSide'] = true;
1968 141
                $mapping['isOwningSide']  = false;
1969
            }
1970 1246
            if (! isset($mapping['orphanRemoval'])) {
1971 1227
                $mapping['orphanRemoval'] = false;
1972
            }
1973
        }
1974
1975 1623
        if (! empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || ! $mapping['isInverseSide'])) {
1976
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
1977
        }
1978
1979 1623
        if ($this->isFile && ! $this->isAllowedGridFSField($mapping['name'])) {
1980 1
            throw MappingException::fieldNotAllowedForGridFS($this->name, $mapping['fieldName']);
1981
        }
1982
1983 1622
        $this->applyStorageStrategy($mapping);
1984 1621
        $this->checkDuplicateMapping($mapping);
1985 1621
        $this->typeRequirementsAreMet($mapping);
1986
1987
        $deprecatedTypes = [
1988 1621
            Type::BOOLEAN => Type::BOOL,
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\ODM\MongoDB\Types\Type::BOOLEAN has been deprecated with message: const was deprecated in 2.1 and will be removed in 3.0. Use Type::BOOL instead

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
1989
            Type::INTEGER => Type::INT,
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\ODM\MongoDB\Types\Type::INTEGER has been deprecated with message: const was deprecated in 2.1 and will be removed in 3.0. Use Type::INT instead

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
1990
        ];
1991 1621
        if (isset($deprecatedTypes[$mapping['type']])) {
1992 3
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1993 3
                '"%s" type was deprecated in 2.1 and will be removed in 3.0. Use "%s" instead.',
1994 3
                $mapping['type'],
1995 3
                $deprecatedTypes[$mapping['type']]
1996
            ));
1997
        }
1998
1999 1621
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
2000 1621
        if (isset($mapping['association'])) {
2001 1410
            $this->associationMappings[$mapping['fieldName']] = $mapping;
2002
        }
2003
2004 1621
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
2005 1620
        $reflProp->setAccessible(true);
2006 1620
        $this->reflFields[$mapping['fieldName']] = $reflProp;
2007
2008 1620
        return $mapping;
2009
    }
2010
2011
    /**
2012
     * Determines which fields get serialized.
2013
     *
2014
     * It is only serialized what is necessary for best unserialization performance.
2015
     * That means any metadata properties that are not set or empty or simply have
2016
     * their default value are NOT serialized.
2017
     *
2018
     * Parts that are also NOT serialized because they can not be properly unserialized:
2019
     *      - reflClass (ReflectionClass)
2020
     *      - reflFields (ReflectionProperty array)
2021
     *
2022
     * @return array The names of all the fields that should be serialized.
2023
     */
2024 6
    public function __sleep()
2025
    {
2026
        // This metadata is always serialized/cached.
2027
        $serialized = [
2028 6
            'fieldMappings',
2029
            'associationMappings',
2030
            'identifier',
2031
            'name',
2032
            'db',
2033
            'collection',
2034
            'readPreference',
2035
            'readPreferenceTags',
2036
            'writeConcern',
2037
            'rootDocumentName',
2038
            'generatorType',
2039
            'generatorOptions',
2040
            'idGenerator',
2041
            'indexes',
2042
            'shardKey',
2043
        ];
2044
2045
        // The rest of the metadata is only serialized if necessary.
2046 6
        if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {
2047
            $serialized[] = 'changeTrackingPolicy';
2048
        }
2049
2050 6
        if ($this->customRepositoryClassName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customRepositoryClassName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2051 1
            $serialized[] = 'customRepositoryClassName';
2052
        }
2053
2054 6
        if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE || $this->discriminatorField !== null) {
2055 4
            $serialized[] = 'inheritanceType';
2056 4
            $serialized[] = 'discriminatorField';
2057 4
            $serialized[] = 'discriminatorValue';
2058 4
            $serialized[] = 'discriminatorMap';
2059 4
            $serialized[] = 'defaultDiscriminatorValue';
2060 4
            $serialized[] = 'parentClasses';
2061 4
            $serialized[] = 'subClasses';
2062
        }
2063
2064 6
        if ($this->isMappedSuperclass) {
2065 1
            $serialized[] = 'isMappedSuperclass';
2066
        }
2067
2068 6
        if ($this->isEmbeddedDocument) {
2069 1
            $serialized[] = 'isEmbeddedDocument';
2070
        }
2071
2072 6
        if ($this->isQueryResultDocument) {
2073
            $serialized[] = 'isQueryResultDocument';
2074
        }
2075
2076 6
        if ($this->isView()) {
2077
            $serialized[] = 'isView';
2078
            $serialized[] = 'rootClass';
2079
        }
2080
2081 6
        if ($this->isFile) {
2082
            $serialized[] = 'isFile';
2083
            $serialized[] = 'bucketName';
2084
            $serialized[] = 'chunkSizeBytes';
2085
        }
2086
2087 6
        if ($this->isVersioned) {
2088
            $serialized[] = 'isVersioned';
2089
            $serialized[] = 'versionField';
2090
        }
2091
2092 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...
2093
            $serialized[] = 'lifecycleCallbacks';
2094
        }
2095
2096 6
        if ($this->collectionCapped) {
2097 1
            $serialized[] = 'collectionCapped';
2098 1
            $serialized[] = 'collectionSize';
2099 1
            $serialized[] = 'collectionMax';
2100
        }
2101
2102 6
        if ($this->isReadOnly) {
2103
            $serialized[] = 'isReadOnly';
2104
        }
2105
2106 6
        return $serialized;
2107
    }
2108
2109
    /**
2110
     * Restores some state that can not be serialized/unserialized.
2111
     */
2112 6
    public function __wakeup()
2113
    {
2114
        // Restore ReflectionClass and properties
2115 6
        $this->reflClass    = new ReflectionClass($this->name);
2116 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...
2117
2118 6
        foreach ($this->fieldMappings as $field => $mapping) {
2119 3
            if (isset($mapping['declared'])) {
2120 1
                $reflField = new ReflectionProperty($mapping['declared'], $field);
2121
            } else {
2122 3
                $reflField = $this->reflClass->getProperty($field);
2123
            }
2124 3
            $reflField->setAccessible(true);
2125 3
            $this->reflFields[$field] = $reflField;
2126
        }
2127 6
    }
2128
2129
    /**
2130
     * Creates a new instance of the mapped class, without invoking the constructor.
2131
     */
2132 374
    public function newInstance() : object
2133
    {
2134 374
        return $this->instantiator->instantiate($this->name);
2135
    }
2136
2137 146
    private function isAllowedGridFSField(string $name) : bool
2138
    {
2139 146
        return in_array($name, self::ALLOWED_GRIDFS_FIELDS, true);
2140
    }
2141
2142 1621
    private function typeRequirementsAreMet(array $mapping) : void
2143
    {
2144 1621
        if ($mapping['type'] === Type::DECIMAL128 && ! extension_loaded('bcmath')) {
2145
            throw MappingException::typeRequirementsNotFulfilled($this->name, $mapping['fieldName'], Type::DECIMAL128, 'ext-bcmath is missing');
2146
        }
2147 1621
    }
2148
2149 1621
    private function checkDuplicateMapping(array $mapping) : void
2150
    {
2151 1621
        if ($mapping['notSaved'] ?? false) {
2152 985
            return;
2153
        }
2154
2155 1621
        foreach ($this->fieldMappings as $fieldName => $otherMapping) {
2156
            // Ignore fields with the same name - we can safely override their mapping
2157 1567
            if ($mapping['fieldName'] === $fieldName) {
2158 135
                continue;
2159
            }
2160
2161
            // Ignore fields with a different name in the database
2162 1563
            if ($mapping['name'] !== $otherMapping['name']) {
2163 1563
                continue;
2164
            }
2165
2166
            // If the other field is not saved, ignore it as well
2167 2
            if ($otherMapping['notSaved'] ?? false) {
2168
                continue;
2169
            }
2170
2171 2
            throw MappingException::duplicateDatabaseFieldName($this->getName(), $mapping['fieldName'], $mapping['name'], $fieldName);
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
2172
        }
2173 1621
    }
2174
}
2175