Completed
Pull Request — master (#2166)
by Andreas
18:35
created

ClassMetadata::getCollectionSize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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