Completed
Pull Request — master (#2128)
by Maciej
24:24
created

ClassMetadata::typeRequirementsAreMet()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

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