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