Completed
Pull Request — master (#2128)
by Maciej
33:47 queued 08:42
created

ClassMetadata::isSharded()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 1
cts 1
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\Incrementable;
13
use Doctrine\ODM\MongoDB\Types\Type;
14
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
15
use Doctrine\Persistence\Mapping\ClassMetadata as BaseClassMetadata;
16
use InvalidArgumentException;
17
use LogicException;
18
use ProxyManager\Proxy\GhostObjectInterface;
19
use ReflectionClass;
20
use ReflectionProperty;
21
use function array_filter;
22
use function array_key_exists;
23
use function array_keys;
24
use function array_map;
25
use function array_pop;
26
use function class_exists;
27
use function constant;
28
use function count;
29
use function extension_loaded;
30
use function get_class;
31
use function in_array;
32
use function is_array;
33
use function is_string;
34
use function is_subclass_of;
35
use function ltrim;
36
use function sprintf;
37
use function strtolower;
38
use function strtoupper;
39
40
/**
41
 * A <tt>ClassMetadata</tt> instance holds all the object-document mapping metadata
42
 * of a document and it's references.
43
 *
44
 * Once populated, ClassMetadata instances are usually cached in a serialized form.
45
 *
46
 * <b>IMPORTANT NOTE:</b>
47
 *
48
 * The fields of this class are only public for 2 reasons:
49
 * 1) To allow fast READ access.
50
 * 2) To drastically reduce the size of a serialized instance (private/protected members
51
 *    get the whole class name, namespace inclusive, prepended to every property in
52
 *    the serialized representation).
53
 */
54
class ClassMetadata implements BaseClassMetadata
55
{
56
    /* The Id generator types. */
57
    /**
58
     * AUTO means Doctrine will automatically create a new \MongoDB\BSON\ObjectId instance for us.
59
     */
60
    public const GENERATOR_TYPE_AUTO = 1;
61
62
    /**
63
     * INCREMENT means a separate collection is used for maintaining and incrementing id generation.
64
     * Offers full portability.
65
     */
66
    public const GENERATOR_TYPE_INCREMENT = 2;
67
68
    /**
69
     * UUID means Doctrine will generate a uuid for us.
70
     */
71
    public const GENERATOR_TYPE_UUID = 3;
72
73
    /**
74
     * ALNUM means Doctrine will generate Alpha-numeric string identifiers, using the INCREMENT
75
     * generator to ensure identifier uniqueness
76
     */
77
    public const GENERATOR_TYPE_ALNUM = 4;
78
79
    /**
80
     * CUSTOM means Doctrine expect a class parameter. It will then try to initiate that class
81
     * and pass other options to the generator. It will throw an Exception if the class
82
     * does not exist or if an option was passed for that there is not setter in the new
83
     * generator class.
84
     *
85
     * The class  will have to be a subtype of AbstractIdGenerator.
86
     */
87
    public const GENERATOR_TYPE_CUSTOM = 5;
88
89
    /**
90
     * NONE means Doctrine will not generate any id for us and you are responsible for manually
91
     * assigning an id.
92
     */
93
    public const GENERATOR_TYPE_NONE = 6;
94
95
    /**
96
     * Default discriminator field name.
97
     *
98
     * This is used for associations value for associations where a that do not define a "targetDocument" or
99
     * "discriminatorField" option in their mapping.
100
     */
101
    public const DEFAULT_DISCRIMINATOR_FIELD = '_doctrine_class_name';
102
103
    public const REFERENCE_ONE  = 1;
104
    public const REFERENCE_MANY = 2;
105
    public const EMBED_ONE      = 3;
106
    public const EMBED_MANY     = 4;
107
    public const MANY           = 'many';
108
    public const ONE            = 'one';
109
110
    /**
111
     * The types of storeAs references
112
     */
113
    public const REFERENCE_STORE_AS_ID             = 'id';
114
    public const REFERENCE_STORE_AS_DB_REF         = 'dbRef';
115
    public const REFERENCE_STORE_AS_DB_REF_WITH_DB = 'dbRefWithDb';
116
    public const REFERENCE_STORE_AS_REF            = 'ref';
117
118
    /* The inheritance mapping types */
119
    /**
120
     * NONE means the class does not participate in an inheritance hierarchy
121
     * and therefore does not need an inheritance mapping type.
122
     */
123
    public const INHERITANCE_TYPE_NONE = 1;
124
125
    /**
126
     * SINGLE_COLLECTION means the class will be persisted according to the rules of
127
     * <tt>Single Collection Inheritance</tt>.
128
     */
129
    public const INHERITANCE_TYPE_SINGLE_COLLECTION = 2;
130
131
    /**
132
     * COLLECTION_PER_CLASS means the class will be persisted according to the rules
133
     * of <tt>Concrete Collection Inheritance</tt>.
134
     */
135
    public const INHERITANCE_TYPE_COLLECTION_PER_CLASS = 3;
136
137
    /**
138
     * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time
139
     * by doing a property-by-property comparison with the original data. This will
140
     * be done for all entities that are in MANAGED state at commit-time.
141
     *
142
     * This is the default change tracking policy.
143
     */
144
    public const CHANGETRACKING_DEFERRED_IMPLICIT = 1;
145
146
    /**
147
     * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time
148
     * by doing a property-by-property comparison with the original data. This will
149
     * be done only for entities that were explicitly saved (through persist() or a cascade).
150
     */
151
    public const CHANGETRACKING_DEFERRED_EXPLICIT = 2;
152
153
    /**
154
     * NOTIFY means that Doctrine relies on the entities sending out notifications
155
     * when their properties change. Such entity classes must implement
156
     * the <tt>NotifyPropertyChanged</tt> interface.
157
     */
158
    public const CHANGETRACKING_NOTIFY = 3;
159
160
    /**
161
     * SET means that fields will be written to the database using a $set operator
162
     */
163
    public const STORAGE_STRATEGY_SET = 'set';
164
165
    /**
166
     * INCREMENT means that fields will be written to the database by calculating
167
     * the difference and using the $inc operator
168
     */
169
    public const STORAGE_STRATEGY_INCREMENT = 'increment';
170
171
    public const STORAGE_STRATEGY_PUSH_ALL         = 'pushAll';
172
    public const STORAGE_STRATEGY_ADD_TO_SET       = 'addToSet';
173
    public const STORAGE_STRATEGY_ATOMIC_SET       = 'atomicSet';
174
    public const STORAGE_STRATEGY_ATOMIC_SET_ARRAY = 'atomicSetArray';
175
    public const STORAGE_STRATEGY_SET_ARRAY        = 'setArray';
176
177
    private const ALLOWED_GRIDFS_FIELDS = ['_id', 'chunkSize', 'filename', 'length', 'metadata', 'uploadDate'];
178
179
    /**
180
     * READ-ONLY: The name of the mongo database the document is mapped to.
181
     *
182
     * @var string|null
183
     */
184
    public $db;
185
186
    /**
187
     * READ-ONLY: The name of the mongo collection the document is mapped to.
188
     *
189
     * @var string
190
     */
191
    public $collection;
192
193
    /**
194
     * READ-ONLY: The name of the GridFS bucket the document is mapped to.
195
     *
196
     * @var string
197
     */
198
    public $bucketName = 'fs';
199
200
    /**
201
     * READ-ONLY: If the collection should be a fixed size.
202
     *
203
     * @var bool
204
     */
205
    public $collectionCapped = false;
206
207
    /**
208
     * READ-ONLY: If the collection is fixed size, its size in bytes.
209
     *
210
     * @var int|null
211
     */
212
    public $collectionSize;
213
214
    /**
215
     * READ-ONLY: If the collection is fixed size, the maximum number of elements to store in the collection.
216
     *
217
     * @var int|null
218
     */
219
    public $collectionMax;
220
221
    /**
222
     * READ-ONLY Describes how MongoDB clients route read operations to the members of a replica set.
223
     *
224
     * @var string|null
225
     */
226
    public $readPreference;
227
228
    /**
229
     * READ-ONLY Associated with readPreference Allows to specify criteria so that your application can target read
230
     * operations to specific members, based on custom parameters.
231
     *
232
     * @var string[][]
233
     */
234
    public $readPreferenceTags = [];
235
236
    /**
237
     * READ-ONLY: Describes the level of acknowledgement requested from MongoDB for write operations.
238
     *
239
     * @var string|int|null
240
     */
241
    public $writeConcern;
242
243
    /**
244
     * READ-ONLY: The field name of the document identifier.
245
     *
246
     * @var string|null
247
     */
248
    public $identifier;
249
250
    /**
251
     * READ-ONLY: The array of indexes for the document collection.
252
     *
253
     * @var array
254
     */
255
    public $indexes = [];
256
257
    /**
258
     * READ-ONLY: Keys and options describing shard key. Only for sharded collections.
259
     *
260
     * @var array<string, array>
261
     */
262
    public $shardKey = [];
263
264
    /**
265
     * READ-ONLY: The name of the document class.
266
     *
267
     * @var string
268
     */
269
    public $name;
270
271
    /**
272
     * READ-ONLY: The name of the document class that is at the root of the mapped document inheritance
273
     * hierarchy. If the document is not part of a mapped inheritance hierarchy this is the same
274
     * as {@link $documentName}.
275
     *
276
     * @var string
277
     */
278
    public $rootDocumentName;
279
280
    /**
281
     * The name of the custom repository class used for the document class.
282
     * (Optional).
283
     *
284
     * @var string|null
285
     */
286
    public $customRepositoryClassName;
287
288
    /**
289
     * READ-ONLY: The names of the parent classes (ancestors).
290
     *
291
     * @var array
292
     */
293
    public $parentClasses = [];
294
295
    /**
296
     * READ-ONLY: The names of all subclasses (descendants).
297
     *
298
     * @var array
299
     */
300
    public $subClasses = [];
301
302
    /**
303
     * The ReflectionProperty instances of the mapped class.
304
     *
305
     * @var ReflectionProperty[]
306
     */
307
    public $reflFields = [];
308
309
    /**
310
     * READ-ONLY: The inheritance mapping type used by the class.
311
     *
312
     * @var int
313
     */
314
    public $inheritanceType = self::INHERITANCE_TYPE_NONE;
315
316
    /**
317
     * READ-ONLY: The Id generator type used by the class.
318
     *
319
     * @var int
320
     */
321
    public $generatorType = self::GENERATOR_TYPE_AUTO;
322
323
    /**
324
     * READ-ONLY: The Id generator options.
325
     *
326
     * @var array
327
     */
328
    public $generatorOptions = [];
329
330
    /**
331
     * READ-ONLY: The ID generator used for generating IDs for this class.
332
     *
333
     * @var AbstractIdGenerator|null
334
     */
335
    public $idGenerator;
336
337
    /**
338
     * READ-ONLY: The field mappings of the class.
339
     * Keys are field names and values are mapping definitions.
340
     *
341
     * The mapping definition array has the following values:
342
     *
343
     * - <b>fieldName</b> (string)
344
     * The name of the field in the Document.
345
     *
346
     * - <b>id</b> (boolean, optional)
347
     * Marks the field as the primary key of the document. Multiple fields of an
348
     * document can have the id attribute, forming a composite key.
349
     *
350
     * @var array
351
     */
352
    public $fieldMappings = [];
353
354
    /**
355
     * READ-ONLY: The association mappings of the class.
356
     * Keys are field names and values are mapping definitions.
357
     *
358
     * @var array
359
     */
360
    public $associationMappings = [];
361
362
    /**
363
     * READ-ONLY: Array of fields to also load with a given method.
364
     *
365
     * @var array
366
     */
367
    public $alsoLoadMethods = [];
368
369
    /**
370
     * READ-ONLY: The registered lifecycle callbacks for documents of this class.
371
     *
372
     * @var array
373
     */
374
    public $lifecycleCallbacks = [];
375
376
    /**
377
     * READ-ONLY: The discriminator value of this class.
378
     *
379
     * <b>This does only apply to the JOINED and SINGLE_COLLECTION inheritance mapping strategies
380
     * where a discriminator field is used.</b>
381
     *
382
     * @see discriminatorField
383
     *
384
     * @var mixed
385
     */
386
    public $discriminatorValue;
387
388
    /**
389
     * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
390
     *
391
     * <b>This does only apply to the SINGLE_COLLECTION inheritance mapping strategy
392
     * where a discriminator field is used.</b>
393
     *
394
     * @see discriminatorField
395
     *
396
     * @var mixed
397
     */
398
    public $discriminatorMap = [];
399
400
    /**
401
     * READ-ONLY: The definition of the discriminator field used in SINGLE_COLLECTION
402
     * inheritance mapping.
403
     *
404
     * @var string|null
405
     */
406
    public $discriminatorField;
407
408
    /**
409
     * READ-ONLY: The default value for discriminatorField in case it's not set in the document
410
     *
411
     * @see discriminatorField
412
     *
413
     * @var string|null
414
     */
415
    public $defaultDiscriminatorValue;
416
417
    /**
418
     * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
419
     *
420
     * @var bool
421
     */
422
    public $isMappedSuperclass = false;
423
424
    /**
425
     * READ-ONLY: Whether this class describes the mapping of a embedded document.
426
     *
427
     * @var bool
428
     */
429
    public $isEmbeddedDocument = false;
430
431
    /**
432
     * READ-ONLY: Whether this class describes the mapping of an aggregation result document.
433
     *
434
     * @var bool
435
     */
436
    public $isQueryResultDocument = false;
437
438
    /**
439
     * READ-ONLY: Whether this class describes the mapping of a database view.
440
     *
441
     * @var bool
442
     */
443
    private $isView = false;
444
445
    /**
446
     * READ-ONLY: Whether this class describes the mapping of a gridFS file
447
     *
448
     * @var bool
449
     */
450
    public $isFile = false;
451
452
    /**
453
     * READ-ONLY: The default chunk size in bytes for the file
454
     *
455
     * @var int|null
456
     */
457
    public $chunkSizeBytes;
458
459
    /**
460
     * READ-ONLY: The policy used for change-tracking on entities of this class.
461
     *
462
     * @var int
463
     */
464
    public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;
465
466
    /**
467
     * READ-ONLY: A flag for whether or not instances of this class are to be versioned
468
     * with optimistic locking.
469
     *
470
     * @var bool $isVersioned
471
     */
472
    public $isVersioned = false;
473
474
    /**
475
     * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
476
     *
477
     * @var string|null $versionField
478
     */
479
    public $versionField;
480
481
    /**
482
     * READ-ONLY: A flag for whether or not instances of this class are to allow pessimistic
483
     * locking.
484
     *
485
     * @var bool $isLockable
486
     */
487
    public $isLockable = false;
488
489
    /**
490
     * READ-ONLY: The name of the field which is used for locking a document.
491
     *
492
     * @var mixed $lockField
493
     */
494
    public $lockField;
495
496
    /**
497
     * The ReflectionClass instance of the mapped class.
498
     *
499
     * @var ReflectionClass
500
     */
501
    public $reflClass;
502
503
    /**
504
     * READ_ONLY: A flag for whether or not this document is read-only.
505
     *
506
     * @var bool
507
     */
508
    public $isReadOnly;
509
510
    /** @var InstantiatorInterface */
511
    private $instantiator;
512
513
    /** @var string|null */
514
    private $rootClass;
515
516
    /**
517
     * Initializes a new ClassMetadata instance that will hold the object-document mapping
518 1681
     * metadata of the class with the given name.
519
     */
520 1681
    public function __construct(string $documentName)
521 1681
    {
522 1681
        $this->name             = $documentName;
523 1681
        $this->rootDocumentName = $documentName;
524 1681
        $this->reflClass        = new ReflectionClass($documentName);
525 1681
        $this->setCollection($this->reflClass->getShortName());
526
        $this->instantiator = new Instantiator();
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...
527
    }
528
529
    /**
530
     * Helper method to get reference id of ref* type references
531
     *
532
     * @internal
533
     *
534
     * @param mixed $reference
535
     *
536 121
     * @return mixed
537
     */
538 121
    public static function getReferenceId($reference, string $storeAs)
539
    {
540
        return $storeAs === self::REFERENCE_STORE_AS_ID ? $reference : $reference[self::getReferencePrefix($storeAs) . 'id'];
541
    }
542
543
    /**
544 189
     * Returns the reference prefix used for a reference
545
     */
546 189
    private static function getReferencePrefix(string $storeAs) : string
547
    {
548
        if (! in_array($storeAs, [self::REFERENCE_STORE_AS_REF, self::REFERENCE_STORE_AS_DB_REF, self::REFERENCE_STORE_AS_DB_REF_WITH_DB])) {
549
            throw new LogicException('Can only get a reference prefix for DBRef and reference arrays');
550 189
        }
551
552
        return $storeAs === self::REFERENCE_STORE_AS_REF ? '' : '$';
553
    }
554
555
    /**
556
     * Returns a fully qualified field name for a given reference
557
     *
558
     * @internal
559
     *
560 145
     * @param string $pathPrefix The field path prefix
561
     */
562 145
    public static function getReferenceFieldName(string $storeAs, string $pathPrefix = '') : string
563 104
    {
564
        if ($storeAs === self::REFERENCE_STORE_AS_ID) {
565
            return $pathPrefix;
566 126
        }
567
568
        return ($pathPrefix ? $pathPrefix . '.' : '') . static::getReferencePrefix($storeAs) . 'id';
0 ignored issues
show
Bug introduced by
Since getReferencePrefix() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getReferencePrefix() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
569
    }
570
571
    /**
572 1601
     * {@inheritDoc}
573
     */
574 1601
    public function getReflectionClass() : ReflectionClass
575
    {
576
        return $this->reflClass;
577
    }
578
579
    /**
580 366
     * {@inheritDoc}
581
     */
582 366
    public function isIdentifier($fieldName) : bool
583
    {
584
        return $this->identifier === $fieldName;
585
    }
586
587
    /**
588
     * Sets the mapped identifier field of this class.
589
     *
590 1035
     * @internal
591
     */
592 1035
    public function setIdentifier(?string $identifier) : void
593 1035
    {
594
        $this->identifier = $identifier;
595
    }
596
597
    /**
598
     * {@inheritDoc}
599
     *
600
     * Since MongoDB only allows exactly one identifier field
601 12
     * this will always return an array with only one value
602
     */
603 12
    public function getIdentifier() : array
604
    {
605
        return [$this->identifier];
606
    }
607
608
    /**
609
     * Since MongoDB only allows exactly one identifier field
610
     * this will always return an array with only one value
611
     *
612 109
     * return (string|null)[]
613
     */
614 109
    public function getIdentifierFieldNames() : array
615
    {
616
        return [$this->identifier];
617
    }
618
619
    /**
620 1011
     * {@inheritDoc}
621
     */
622 1011
    public function hasField($fieldName) : bool
623
    {
624
        return isset($this->fieldMappings[$fieldName]);
625
    }
626
627
    /**
628 1056
     * Sets the inheritance type used by the class and it's subclasses.
629
     */
630 1056
    public function setInheritanceType(int $type) : void
631 1056
    {
632
        $this->inheritanceType = $type;
633
    }
634
635
    /**
636 1587
     * Checks whether a mapped field is inherited from an entity superclass.
637
     */
638 1587
    public function isInheritedField(string $fieldName) : bool
639
    {
640
        return isset($this->fieldMappings[$fieldName]['inherited']);
641
    }
642
643
    /**
644 990
     * Registers a custom repository class for the document class.
645
     */
646 990
    public function setCustomRepositoryClass(?string $repositoryClassName) : void
647
    {
648
        if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
649
            return;
650 990
        }
651 990
652
        $this->customRepositoryClassName = $repositoryClassName;
653
    }
654
655
    /**
656
     * Dispatches the lifecycle event of the given document by invoking all
657
     * registered callbacks.
658
     *
659
     * @throws InvalidArgumentException If document class is not this class or
660 667
     *                                   a Proxy of this class.
661
     */
662 667
    public function invokeLifecycleCallbacks(string $event, object $document, ?array $arguments = null) : void
663
    {
664
        if ($this->isView()) {
665
            return;
666 667
        }
667 1
668
        if (! $document instanceof $this->name) {
669
            throw new InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"', $this->name, get_class($document)));
670 666
        }
671 651
672
        if (empty($this->lifecycleCallbacks[$event])) {
673
            return;
674 193
        }
675 193
676 192
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
677
            if ($arguments !== null) {
678 2
                $document->$callback(...$arguments);
679
            } else {
680
                $document->$callback();
681 193
            }
682
        }
683
    }
684
685
    /**
686
     * Checks whether the class has callbacks registered for a lifecycle event.
687
     */
688
    public function hasLifecycleCallbacks(string $event) : bool
689
    {
690
        return ! empty($this->lifecycleCallbacks[$event]);
691
    }
692
693
    /**
694
     * Gets the registered lifecycle callbacks for an event.
695
     */
696
    public function getLifecycleCallbacks(string $event) : array
697
    {
698
        return $this->lifecycleCallbacks[$event] ?? [];
699
    }
700
701
    /**
702
     * Adds a lifecycle callback for documents of this class.
703
     *
704 940
     * If the callback is already registered, this is a NOOP.
705
     */
706 940
    public function addLifecycleCallback(string $callback, string $event) : void
707 1
    {
708
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
709
            return;
710 940
        }
711 940
712
        $this->lifecycleCallbacks[$event][] = $callback;
713
    }
714
715
    /**
716
     * Sets the lifecycle callbacks for documents of this class.
717
     *
718 1034
     * Any previously registered callbacks are overwritten.
719
     */
720 1034
    public function setLifecycleCallbacks(array $callbacks) : void
721 1034
    {
722
        $this->lifecycleCallbacks = $callbacks;
723
    }
724
725
    /**
726
     * Registers a method for loading document data before field hydration.
727
     *
728
     * Note: A method may be registered multiple times for different fields.
729
     * it will be invoked only once for the first field found.
730
     *
731 14
     * @param array|string $fields Database field name(s)
732
     */
733 14
    public function registerAlsoLoadMethod(string $method, $fields) : void
734 14
    {
735
        $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : [$fields];
736
    }
737
738
    /**
739
     * Sets the AlsoLoad methods for documents of this class.
740
     *
741 1034
     * Any previously registered methods are overwritten.
742
     */
743 1034
    public function setAlsoLoadMethods(array $methods) : void
744 1034
    {
745
        $this->alsoLoadMethods = $methods;
746
    }
747
748
    /**
749
     * Sets the discriminator field.
750
     *
751
     * The field name is the the unmapped database field. Discriminator values
752
     * are only used to discern the hydration class and are not mapped to class
753
     * properties.
754
     *
755
     * @param array|string|null $discriminatorField
756
     *
757
     * @throws MappingException If the discriminator field conflicts with the
758 1065
     *                          "name" attribute of a mapped field.
759
     */
760 1065
    public function setDiscriminatorField($discriminatorField) : void
761
    {
762
        if ($this->isFile) {
763
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
764 1065
        }
765 985
766
        if ($discriminatorField === null) {
767 985
            $this->discriminatorField = null;
768
769
            return;
770
        }
771
772 210
        // @todo: deprecate, document and remove this:
773
        // Handle array argument with name/fieldName keys for BC
774
        if (is_array($discriminatorField)) {
775
            if (isset($discriminatorField['name'])) {
776
                $discriminatorField = $discriminatorField['name'];
777
            } elseif (isset($discriminatorField['fieldName'])) {
778
                $discriminatorField = $discriminatorField['fieldName'];
779
            }
780 210
        }
781 4
782 1
        foreach ($this->fieldMappings as $fieldMapping) {
783
            if ($discriminatorField === $fieldMapping['name']) {
784
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
785
            }
786 209
        }
787 209
788
        $this->discriminatorField = $discriminatorField;
789
    }
790
791
    /**
792
     * Sets the discriminator values used by this class.
793
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
794
     *
795 1054
     * @throws MappingException
796
     */
797 1054
    public function setDiscriminatorMap(array $map) : void
798
    {
799
        if ($this->isFile) {
800
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
801 1054
        }
802 1054
803 1054
        $this->subClasses         = [];
804
        $this->discriminatorMap   = [];
805 1054
        $this->discriminatorValue = null;
806 197
807 197
        foreach ($map as $value => $className) {
808 188
            $this->discriminatorMap[$value] = $className;
809
            if ($this->name === $className) {
810 195
                $this->discriminatorValue = $value;
811
            } else {
812
                if (! class_exists($className)) {
813 195
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
814 180
                }
815
                if (is_subclass_of($className, $this->name)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $this->name can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
816
                    $this->subClasses[] = $className;
817
                }
818 1054
            }
819
        }
820
    }
821
822
    /**
823
     * Sets the default discriminator value to be used for this class
824
     * Used for SINGLE_TABLE inheritance mapping strategies if the document has no discriminator value
825
     *
826 1037
     * @throws MappingException
827
     */
828 1037
    public function setDefaultDiscriminatorValue(?string $defaultDiscriminatorValue) : void
829
    {
830
        if ($this->isFile) {
831
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
832 1037
        }
833 1034
834
        if ($defaultDiscriminatorValue === null) {
835 1034
            $this->defaultDiscriminatorValue = null;
836
837
            return;
838 129
        }
839
840
        if (! array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
841
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
842 129
        }
843 129
844
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
845
    }
846
847
    /**
848
     * Sets the discriminator value for this class.
849
     * Used for JOINED/SINGLE_TABLE inheritance and multiple document types in a single
850
     * collection.
851
     *
852 4
     * @throws MappingException
853
     */
854 4
    public function setDiscriminatorValue(string $value) : void
855
    {
856
        if ($this->isFile) {
857
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
858 4
        }
859 4
860 4
        $this->discriminatorMap[$value] = $this->name;
861
        $this->discriminatorValue       = $value;
862
    }
863
864
    /**
865 283
     * Add a index for this Document.
866
     */
867 283
    public function addIndex(array $keys, array $options = []) : void
868
    {
869 283
        $this->indexes[] = [
870 126
            'keys' => array_map(static function ($value) {
871
                if ($value === 1 || $value === -1) {
872 283
                    return $value;
873 283
                }
874 283
                if (is_string($value)) {
875 276
                    $lower = strtolower($value);
876
                    if ($lower === 'asc') {
877
                        return 1;
878 133
                    }
879
880
                    if ($lower === 'desc') {
881
                        return -1;
882
                    }
883 133
                }
884 283
885 283
                return $value;
886
            }, $keys),
887 283
            'options' => $options,
888
        ];
889
    }
890
891
    /**
892 50
     * Returns the array of indexes for this Document.
893
     */
894 50
    public function getIndexes() : array
895
    {
896
        return $this->indexes;
897
    }
898
899
    /**
900
     * Checks whether this document has indexes or not.
901
     */
902
    public function hasIndexes() : bool
903
    {
904
        return $this->indexes !== [];
905
    }
906
907
    /**
908
     * Set shard key for this Document.
909
     *
910 162
     * @throws MappingException
911
     */
912 162
    public function setShardKey(array $keys, array $options = []) : void
913 2
    {
914
        if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && $this->shardKey !== []) {
915
            throw MappingException::shardKeyInSingleCollInheritanceSubclass($this->getName());
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
916 162
        }
917 2
918
        if ($this->isEmbeddedDocument) {
919
            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...
920 160
        }
921 160
922 153
        foreach (array_keys($keys) as $field) {
923
            if (! isset($this->fieldMappings[$field])) {
924
                continue;
925 132
            }
926 3
927
            if (in_array($this->fieldMappings[$field]['type'], ['many', 'collection'])) {
928
                throw MappingException::noMultiKeyShardKeys($this->getName(), $field);
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
929 129
            }
930 1
931
            if ($this->fieldMappings[$field]['strategy'] !== self::STORAGE_STRATEGY_SET) {
932
                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...
933
            }
934 156
        }
935
936 156
        $this->shardKey = [
937 5
            'keys' => array_map(static function ($value) {
938
                if ($value === 1 || $value === -1) {
939 156
                    return $value;
940 156
                }
941 156
                if (is_string($value)) {
942 154
                    $lower = strtolower($value);
943
                    if ($lower === 'asc') {
944
                        return 1;
945 128
                    }
946
947
                    if ($lower === 'desc') {
948
                        return -1;
949
                    }
950 128
                }
951 156
952 156
                return $value;
953
            }, $keys),
954 156
            'options' => $options,
955
        ];
956 27
    }
957
958 27
    public function getShardKey() : array
959
    {
960
        return $this->shardKey;
961
    }
962
963
    /**
964 1289
     * Checks whether this document has shard key or not.
965
     */
966 1289
    public function isSharded() : bool
967
    {
968
        return $this->shardKey !== [];
969
    }
970
971
    /**
972 1034
     * Sets the read preference used by this class.
973
     */
974 1034
    public function setReadPreference(?string $readPreference, array $tags) : void
975 1034
    {
976 1034
        $this->readPreference     = $readPreference;
977
        $this->readPreferenceTags = $tags;
978
    }
979
980
    /**
981
     * Sets the write concern used by this class.
982
     *
983 1044
     * @param string|int|null $writeConcern
984
     */
985 1044
    public function setWriteConcern($writeConcern) : void
986 1044
    {
987
        $this->writeConcern = $writeConcern;
988
    }
989
990
    /**
991 11
     * @return int|string|null
992
     */
993 11
    public function getWriteConcern()
994
    {
995
        return $this->writeConcern;
996
    }
997
998
    /**
999 615
     * Whether there is a write concern configured for this class.
1000
     */
1001 615
    public function hasWriteConcern() : bool
1002
    {
1003
        return $this->writeConcern !== null;
1004
    }
1005
1006
    /**
1007 1035
     * Sets the change tracking policy used by this class.
1008
     */
1009 1035
    public function setChangeTrackingPolicy(int $policy) : void
1010 1035
    {
1011
        $this->changeTrackingPolicy = $policy;
1012
    }
1013
1014
    /**
1015 70
     * Whether the change tracking policy of this class is "deferred explicit".
1016
     */
1017 70
    public function isChangeTrackingDeferredExplicit() : bool
1018
    {
1019
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT;
1020
    }
1021
1022
    /**
1023 626
     * Whether the change tracking policy of this class is "deferred implicit".
1024
     */
1025 626
    public function isChangeTrackingDeferredImplicit() : bool
1026
    {
1027
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT;
1028
    }
1029
1030
    /**
1031 346
     * Whether the change tracking policy of this class is "notify".
1032
     */
1033 346
    public function isChangeTrackingNotify() : bool
1034
    {
1035
        return $this->changeTrackingPolicy === self::CHANGETRACKING_NOTIFY;
1036
    }
1037
1038
    /**
1039 1
     * Gets the ReflectionProperties of the mapped class.
1040
     */
1041 1
    public function getReflectionProperties() : array
1042
    {
1043
        return $this->reflFields;
1044
    }
1045
1046
    /**
1047 108
     * Gets a ReflectionProperty for a specific field of the mapped class.
1048
     */
1049 108
    public function getReflectionProperty(string $name) : ReflectionProperty
1050
    {
1051
        return $this->reflFields[$name];
1052
    }
1053
1054
    /**
1055 1609
     * {@inheritDoc}
1056
     */
1057 1609
    public function getName() : string
1058
    {
1059
        return $this->name;
1060
    }
1061
1062
    /**
1063 1495
     * Returns the database this Document is mapped to.
1064
     */
1065 1495
    public function getDatabase() : ?string
1066
    {
1067
        return $this->db;
1068
    }
1069
1070
    /**
1071 179
     * Set the database this Document is mapped to.
1072
     */
1073 179
    public function setDatabase(?string $db) : void
1074 179
    {
1075
        $this->db = $db;
1076
    }
1077
1078
    /**
1079 1494
     * Get the collection this Document is mapped to.
1080
     */
1081 1494
    public function getCollection() : string
1082
    {
1083
        return $this->collection;
1084
    }
1085
1086
    /**
1087
     * Sets the collection this Document is mapped to.
1088
     *
1089
     * @param array|string $name
1090
     *
1091 1681
     * @throws InvalidArgumentException
1092
     */
1093 1681
    public function setCollection($name) : void
1094 1
    {
1095
        if (is_array($name)) {
1096
            if (! isset($name['name'])) {
1097 1
                throw new InvalidArgumentException('A name key is required when passing an array to setCollection()');
1098 1
            }
1099 1
            $this->collectionCapped = $name['capped'] ?? false;
1100 1
            $this->collectionSize   = $name['size'] ?? 0;
1101
            $this->collectionMax    = $name['max'] ?? 0;
1102 1681
            $this->collection       = $name['name'];
1103
        } else {
1104 1681
            $this->collection = $name;
1105
        }
1106 137
    }
1107
1108 137
    public function getBucketName() : ?string
1109
    {
1110
        return $this->bucketName;
1111 1
    }
1112
1113 1
    public function setBucketName(string $bucketName) : void
1114 1
    {
1115 1
        $this->bucketName = $bucketName;
1116
        $this->setCollection($bucketName . '.files');
1117 12
    }
1118
1119 12
    public function getChunkSizeBytes() : ?int
1120
    {
1121
        return $this->chunkSizeBytes;
1122 140
    }
1123
1124 140
    public function setChunkSizeBytes(int $chunkSizeBytes) : void
1125 140
    {
1126
        $this->chunkSizeBytes = $chunkSizeBytes;
1127
    }
1128
1129
    /**
1130 11
     * Get whether or not the documents collection is capped.
1131
     */
1132 11
    public function getCollectionCapped() : bool
1133
    {
1134
        return $this->collectionCapped;
1135
    }
1136
1137
    /**
1138 1
     * Set whether or not the documents collection is capped.
1139
     */
1140 1
    public function setCollectionCapped(bool $bool) : void
1141 1
    {
1142
        $this->collectionCapped = $bool;
1143
    }
1144
1145
    /**
1146 11
     * Get the collection size
1147
     */
1148 11
    public function getCollectionSize() : ?int
1149
    {
1150
        return $this->collectionSize;
1151
    }
1152
1153
    /**
1154 1
     * Set the collection size.
1155
     */
1156 1
    public function setCollectionSize(int $size) : void
1157 1
    {
1158
        $this->collectionSize = $size;
1159
    }
1160
1161
    /**
1162 11
     * Get the collection max.
1163
     */
1164 11
    public function getCollectionMax() : ?int
1165
    {
1166
        return $this->collectionMax;
1167
    }
1168
1169
    /**
1170 1
     * Set the collection max.
1171
     */
1172 1
    public function setCollectionMax(int $max) : void
1173 1
    {
1174
        $this->collectionMax = $max;
1175
    }
1176
1177
    /**
1178
     * Returns TRUE if this Document is mapped to a collection FALSE otherwise.
1179
     */
1180
    public function isMappedToCollection() : bool
1181
    {
1182
        return $this->collection !== '' && $this->collection !== null;
1183
    }
1184
1185
    /**
1186
     * Validates the storage strategy of a mapping for consistency
1187
     *
1188 1619
     * @throws MappingException
1189
     */
1190 1619
    private function applyStorageStrategy(array &$mapping) : void
1191 1600
    {
1192
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1193
            return;
1194
        }
1195 1576
1196 1575
        switch (true) {
1197 990
            case $mapping['type'] === 'many':
1198 990
                $defaultStrategy   = CollectionHelper::DEFAULT_STRATEGY;
1199 990
                $allowedStrategies = [
1200
                    self::STORAGE_STRATEGY_PUSH_ALL,
1201 1575
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1202 1252
                    self::STORAGE_STRATEGY_SET,
1203
                    self::STORAGE_STRATEGY_SET_ARRAY,
1204 1252
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1205 1252
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1206 1252
                ];
1207 1252
                break;
1208 1252
1209 1252
            case $mapping['type'] === 'one':
1210
                $defaultStrategy   = self::STORAGE_STRATEGY_SET;
1211 1252
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1212
                break;
1213
1214 1562
            default:
1215 1562
                $defaultStrategy   = self::STORAGE_STRATEGY_SET;
1216
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1217
                $type              = Type::getType($mapping['type']);
1218 1576
                if ($type instanceof Incrementable) {
1219 1568
                    $allowedStrategies[] = self::STORAGE_STRATEGY_INCREMENT;
1220
                }
1221
        }
1222 1576
1223
        if (! isset($mapping['strategy'])) {
1224
            $mapping['strategy'] = $defaultStrategy;
1225
        }
1226 1576
1227 1576
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1228 1
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1229
        }
1230 1575
1231
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1232
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1233
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1234
        }
1235 6
    }
1236
1237 6
    /**
1238 6
     * Map a single embedded document.
1239 6
     */
1240 5
    public function mapOneEmbedded(array $mapping) : void
1241
    {
1242
        $mapping['embedded'] = true;
1243
        $mapping['type']     = 'one';
1244
        $this->mapField($mapping);
1245 6
    }
1246
1247 6
    /**
1248 6
     * Map a collection of embedded documents.
1249 6
     */
1250 6
    public function mapManyEmbedded(array $mapping) : void
1251
    {
1252
        $mapping['embedded'] = true;
1253
        $mapping['type']     = 'many';
1254
        $this->mapField($mapping);
1255 3
    }
1256
1257 3
    /**
1258 3
     * Map a single document reference.
1259 3
     */
1260 3
    public function mapOneReference(array $mapping) : void
1261
    {
1262
        $mapping['reference'] = true;
1263
        $mapping['type']      = 'one';
1264
        $this->mapField($mapping);
1265 1
    }
1266
1267 1
    /**
1268 1
     * Map a collection of document references.
1269 1
     */
1270 1
    public function mapManyReference(array $mapping) : void
1271
    {
1272
        $mapping['reference'] = true;
1273
        $mapping['type']      = 'many';
1274
        $this->mapField($mapping);
1275
    }
1276
1277
    /**
1278 205
     * Adds a field mapping without completing/validating it.
1279
     * This is mainly used to add inherited field mappings to derived classes.
1280 205
     *
1281
     * @internal
1282 205
     */
1283 205
    public function addInheritedFieldMapping(array $fieldMapping) : void
1284
    {
1285
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1286 153
1287 153
        if (! isset($fieldMapping['association'])) {
1288
            return;
1289
        }
1290
1291
        $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1292
    }
1293
1294
    /**
1295
     * Adds an association mapping without completing/validating it.
1296
     * This is mainly used to add inherited association mappings to derived classes.
1297 154
     *
1298
     * @internal
1299 154
     *
1300 154
     * @throws MappingException
1301
     */
1302
    public function addInheritedAssociationMapping(array $mapping) : void
1303
    {
1304
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1305 33
    }
1306
1307 33
    /**
1308
     * Checks whether the class has a mapped association with the given field name.
1309
     */
1310
    public function hasReference(string $fieldName) : bool
1311
    {
1312
        return isset($this->fieldMappings[$fieldName]['reference']);
1313 4
    }
1314
1315 4
    /**
1316
     * Checks whether the class has a mapped embed with the given field name.
1317
     */
1318
    public function hasEmbed(string $fieldName) : bool
1319
    {
1320
        return isset($this->fieldMappings[$fieldName]['embedded']);
1321
    }
1322
1323 6
    /**
1324
     * {@inheritDoc}
1325 6
     *
1326
     * Checks whether the class has a mapped association (embed or reference) with the given field name.
1327
     */
1328
    public function hasAssociation($fieldName) : bool
1329
    {
1330
        return $this->hasReference($fieldName) || $this->hasEmbed($fieldName);
1331
    }
1332
1333
    /**
1334
     * {@inheritDoc}
1335
     *
1336
     * Checks whether the class has a mapped reference or embed for the specified field and
1337
     * is a single valued association.
1338
     */
1339
    public function isSingleValuedAssociation($fieldName) : bool
1340
    {
1341
        return $this->isSingleValuedReference($fieldName) || $this->isSingleValuedEmbed($fieldName);
1342
    }
1343
1344
    /**
1345
     * {@inheritDoc}
1346
     *
1347
     * Checks whether the class has a mapped reference or embed for the specified field and
1348
     * is a collection valued association.
1349
     */
1350
    public function isCollectionValuedAssociation($fieldName) : bool
1351
    {
1352
        return $this->isCollectionValuedReference($fieldName) || $this->isCollectionValuedEmbed($fieldName);
1353
    }
1354 1
1355
    /**
1356 1
     * Checks whether the class has a mapped association for the specified field
1357 1
     * and if yes, checks whether it is a single-valued association (to-one).
1358
     */
1359
    public function isSingleValuedReference(string $fieldName) : bool
1360
    {
1361
        return isset($this->fieldMappings[$fieldName]['association']) &&
1362
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_ONE;
1363
    }
1364
1365
    /**
1366
     * Checks whether the class has a mapped association for the specified field
1367
     * and if yes, checks whether it is a collection-valued association (to-many).
1368
     */
1369
    public function isCollectionValuedReference(string $fieldName) : bool
1370
    {
1371
        return isset($this->fieldMappings[$fieldName]['association']) &&
1372
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_MANY;
1373
    }
1374
1375
    /**
1376
     * Checks whether the class has a mapped embedded document for the specified field
1377
     * and if yes, checks whether it is a single-valued association (to-one).
1378
     */
1379
    public function isSingleValuedEmbed(string $fieldName) : bool
1380
    {
1381
        return isset($this->fieldMappings[$fieldName]['association']) &&
1382
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_ONE;
1383
    }
1384
1385
    /**
1386
     * Checks whether the class has a mapped embedded document for the specified field
1387
     * and if yes, checks whether it is a collection-valued association (to-many).
1388
     */
1389
    public function isCollectionValuedEmbed(string $fieldName) : bool
1390
    {
1391
        return isset($this->fieldMappings[$fieldName]['association']) &&
1392
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_MANY;
1393 1543
    }
1394
1395 1543
    /**
1396 1543
     * Sets the ID generator used to generate IDs for instances of this class.
1397
     */
1398
    public function setIdGenerator(AbstractIdGenerator $generator) : void
1399
    {
1400
        $this->idGenerator = $generator;
1401
    }
1402
1403
    /**
1404
     * Casts the identifier to its portable PHP type.
1405 667
     *
1406
     * @param mixed $id
1407 667
     *
1408
     * @return mixed $id
1409 667
     */
1410
    public function getPHPIdentifierValue($id)
1411
    {
1412
        $idType = $this->fieldMappings[$this->identifier]['type'];
1413
1414
        return Type::getType($idType)->convertToPHPValue($id);
1415
    }
1416
1417
    /**
1418
     * Casts the identifier to its database type.
1419 737
     *
1420
     * @param mixed $id
1421 737
     *
1422
     * @return mixed $id
1423 737
     */
1424
    public function getDatabaseIdentifierValue($id)
1425
    {
1426
        $idType = $this->fieldMappings[$this->identifier]['type'];
1427
1428
        return Type::getType($idType)->convertToDatabaseValue($id);
1429
    }
1430
1431
    /**
1432
     * Sets the document identifier of a document.
1433 597
     *
1434
     * The value will be converted to a PHP type before being set.
1435 597
     *
1436 597
     * @param mixed $id
1437 597
     */
1438
    public function setIdentifierValue(object $document, $id) : void
1439
    {
1440
        $id = $this->getPHPIdentifierValue($id);
1441
        $this->reflFields[$this->identifier]->setValue($document, $id);
1442
    }
1443
1444 675
    /**
1445
     * Gets the document identifier as a PHP type.
1446 675
     *
1447
     * @return mixed $id
1448
     */
1449
    public function getIdentifierValue(object $document)
1450
    {
1451
        return $this->reflFields[$this->identifier]->getValue($document);
1452
    }
1453
1454
    /**
1455
     * {@inheritDoc}
1456
     *
1457
     * Since MongoDB only allows exactly one identifier field this is a proxy
1458
     * to {@see getIdentifierValue()} and returns an array with the identifier
1459
     * field as a key.
1460
     */
1461
    public function getIdentifierValues($object) : array
1462
    {
1463
        return [$this->identifier => $this->getIdentifierValue($object)];
1464
    }
1465
1466 31
    /**
1467
     * Get the document identifier object as a database type.
1468 31
     *
1469
     * @return mixed $id
1470
     */
1471
    public function getIdentifierObject(object $document)
1472
    {
1473
        return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));
1474
    }
1475
1476 8
    /**
1477
     * Sets the specified field to the specified value on the given document.
1478 8
     *
1479
     * @param mixed $value
1480
     */
1481 1
    public function setFieldValue(object $document, string $field, $value) : void
1482
    {
1483
        if ($document instanceof GhostObjectInterface && ! $document->isProxyInitialized()) {
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...
1484 8
            //property changes to an uninitialized proxy will not be tracked or persisted,
1485 8
            //so the proxy needs to be loaded first.
1486
            $document->initializeProxy();
1487
        }
1488
1489
        $this->reflFields[$field]->setValue($document, $value);
1490
    }
1491
1492 33
    /**
1493
     * Gets the specified field's value off the given document.
1494 33
     *
1495 1
     * @return mixed
1496
     */
1497
    public function getFieldValue(object $document, string $field)
1498 33
    {
1499
        if ($document instanceof GhostObjectInterface && $field !== $this->identifier && ! $document->isProxyInitialized()) {
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...
1500
            $document->initializeProxy();
1501
        }
1502
1503
        return $this->reflFields[$field]->getValue($document);
1504
    }
1505
1506 201
    /**
1507
     * Gets the mapping of a field.
1508 201
     *
1509 6
     * @throws MappingException If the $fieldName is not found in the fieldMappings array.
1510
     */
1511
    public function getFieldMapping(string $fieldName) : array
1512 199
    {
1513
        if (! isset($this->fieldMappings[$fieldName])) {
1514
            throw MappingException::mappingNotFound($this->name, $fieldName);
1515
        }
1516
1517
        return $this->fieldMappings[$fieldName];
1518 604
    }
1519
1520 604
    /**
1521 604
     * Gets mappings of fields holding embedded document(s).
1522
     */
1523 464
    public function getEmbeddedFieldsMappings() : array
1524 604
    {
1525
        return array_filter(
1526
            $this->associationMappings,
1527
            static function ($assoc) {
1528
                return ! empty($assoc['embedded']);
1529
            }
1530
        );
1531
    }
1532
1533
    /**
1534 17
     * Gets the field mapping by its DB name.
1535
     * E.g. it returns identifier's mapping when called with _id.
1536 17
     *
1537 17
     * @throws MappingException
1538 15
     */
1539
    public function getFieldMappingByDbFieldName(string $dbFieldName) : array
1540
    {
1541
        foreach ($this->fieldMappings as $mapping) {
1542 2
            if ($mapping['name'] === $dbFieldName) {
1543
                return $mapping;
1544
            }
1545
        }
1546
1547
        throw MappingException::mappingNotFoundByDbName($this->name, $dbFieldName);
1548 1
    }
1549
1550 1
    /**
1551
     * Check if the field is not null.
1552 1
     */
1553
    public function isNullable(string $fieldName) : bool
1554
    {
1555
        $mapping = $this->getFieldMapping($fieldName);
1556
1557
        return isset($mapping['nullable']) && $mapping['nullable'] === true;
1558
    }
1559
1560
    /**
1561
     * Checks whether the document has a discriminator field and value configured.
1562
     */
1563
    public function hasDiscriminator() : bool
1564
    {
1565
        return isset($this->discriminatorField, $this->discriminatorValue);
1566 1034
    }
1567
1568 1034
    /**
1569 1034
     * Sets the type of Id generator to use for the mapped class.
1570
     */
1571
    public function setIdGeneratorType(int $generatorType) : void
1572
    {
1573
        $this->generatorType = $generatorType;
1574
    }
1575
1576
    /**
1577
     * Sets the Id generator options.
1578
     */
1579 631
    public function setIdGeneratorOptions(array $generatorOptions) : void
1580
    {
1581 631
        $this->generatorOptions = $generatorOptions;
1582
    }
1583
1584
    public function isInheritanceTypeNone() : bool
1585
    {
1586
        return $this->inheritanceType === self::INHERITANCE_TYPE_NONE;
1587 1031
    }
1588
1589 1031
    /**
1590
     * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.
1591
     */
1592
    public function isInheritanceTypeSingleCollection() : bool
1593
    {
1594
        return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION;
1595
    }
1596
1597
    /**
1598
     * Checks whether the mapped class uses the COLLECTION_PER_CLASS inheritance mapping strategy.
1599
     */
1600
    public function isInheritanceTypeCollectionPerClass() : bool
1601
    {
1602
        return $this->inheritanceType === self::INHERITANCE_TYPE_COLLECTION_PER_CLASS;
1603
    }
1604
1605 2
    /**
1606
     * Sets the mapped subclasses of this class.
1607 2
     *
1608 2
     * @param string[] $subclasses The names of all mapped subclasses.
1609
     */
1610 2
    public function setSubclasses(array $subclasses) : void
1611
    {
1612
        foreach ($subclasses as $subclass) {
1613
            $this->subClasses[] = $subclass;
1614
        }
1615
    }
1616
1617
    /**
1618
     * Sets the parent class names.
1619 1596
     * Assumes that the class names in the passed array are in the order:
1620
     * directParent -> directParentParent -> directParentParentParent ... -> root.
1621 1596
     *
1622
     * @param string[] $classNames
1623 1596
     */
1624 1595
    public function setParentClasses(array $classNames) : void
1625
    {
1626
        $this->parentClasses = $classNames;
1627 187
1628 187
        if (count($classNames) <= 0) {
1629
            return;
1630
        }
1631
1632
        $this->rootDocumentName = (string) array_pop($classNames);
1633
    }
1634
1635
    /**
1636
     * Checks whether the class will generate a new \MongoDB\BSON\ObjectId instance for us.
1637
     */
1638
    public function isIdGeneratorAuto() : bool
1639
    {
1640
        return $this->generatorType === self::GENERATOR_TYPE_AUTO;
1641
    }
1642
1643
    /**
1644
     * Checks whether the class will use a collection to generate incremented identifiers.
1645
     */
1646
    public function isIdGeneratorIncrement() : bool
1647
    {
1648
        return $this->generatorType === self::GENERATOR_TYPE_INCREMENT;
1649
    }
1650
1651
    /**
1652
     * Checks whether the class will generate a uuid id.
1653
     */
1654
    public function isIdGeneratorUuid() : bool
1655
    {
1656
        return $this->generatorType === self::GENERATOR_TYPE_UUID;
1657
    }
1658
1659
    /**
1660
     * Checks whether the class uses no id generator.
1661
     */
1662
    public function isIdGeneratorNone() : bool
1663
    {
1664
        return $this->generatorType === self::GENERATOR_TYPE_NONE;
1665
    }
1666
1667
    /**
1668 172
     * Sets the version field mapping used for versioning. Sets the default
1669
     * value to use depending on the column type.
1670 172
     *
1671 1
     * @throws LockException
1672
     */
1673
    public function setVersionMapping(array &$mapping) : void
1674 171
    {
1675 171
        if (! in_array($mapping['type'], [Type::INT, Type::INTEGER, Type::DATE, Type::DATE_IMMUTABLE, Type::DECIMAL128], true)) {
1676 171
            throw LockException::invalidVersionFieldType($mapping['type']);
1677
        }
1678
1679
        $this->isVersioned  = true;
1680
        $this->versionField = $mapping['fieldName'];
1681 1034
    }
1682
1683 1034
    /**
1684 1034
     * Sets whether this class is to be versioned for optimistic locking.
1685
     */
1686
    public function setVersioned(bool $bool) : void
1687
    {
1688
        $this->isVersioned = $bool;
1689
    }
1690 1034
1691
    /**
1692 1034
     * Sets the name of the field that is to be used for versioning if this class is
1693 1034
     * versioned for optimistic locking.
1694
     */
1695
    public function setVersionField(?string $versionField) : void
1696
    {
1697
        $this->versionField = $versionField;
1698
    }
1699
1700
    /**
1701 25
     * Sets the version field mapping used for versioning. Sets the default
1702
     * value to use depending on the column type.
1703 25
     *
1704 1
     * @throws LockException
1705
     */
1706
    public function setLockMapping(array &$mapping) : void
1707 24
    {
1708 24
        if ($mapping['type'] !== 'int') {
1709 24
            throw LockException::invalidLockFieldType($mapping['type']);
1710
        }
1711
1712
        $this->isLockable = true;
1713
        $this->lockField  = $mapping['fieldName'];
1714
    }
1715
1716
    /**
1717
     * Sets whether this class is to allow pessimistic locking.
1718
     */
1719
    public function setLockable(bool $bool) : void
1720
    {
1721
        $this->isLockable = $bool;
1722
    }
1723
1724
    /**
1725
     * Sets the name of the field that is to be used for storing whether a document
1726
     * is currently locked or not.
1727
     */
1728
    public function setLockField(string $lockField) : void
1729
    {
1730
        $this->lockField = $lockField;
1731 5
    }
1732
1733 5
    /**
1734 5
     * Marks this class as read only, no change tracking is applied to it.
1735
     */
1736 11
    public function markReadOnly() : void
1737
    {
1738 11
        $this->isReadOnly = true;
1739
    }
1740
1741 1591
    public function getRootClass() : ?string
1742
    {
1743 1591
        return $this->rootClass;
1744
    }
1745
1746 133
    public function isView() : bool
1747
    {
1748 133
        return $this->isView;
1749 133
    }
1750 133
1751
    public function markViewOf(string $rootClass) : void
1752
    {
1753
        $this->isView    = true;
1754
        $this->rootClass = $rootClass;
1755
    }
1756
1757
    /**
1758
     * {@inheritDoc}
1759
     */
1760
    public function getFieldNames() : array
1761
    {
1762
        return array_keys($this->fieldMappings);
1763
    }
1764
1765
    /**
1766
     * {@inheritDoc}
1767
     */
1768
    public function getAssociationNames() : array
1769
    {
1770
        return array_keys($this->associationMappings);
1771
    }
1772
1773
    /**
1774
     * {@inheritDoc}
1775
     */
1776
    public function getTypeOfField($fieldName) : ?string
1777
    {
1778
        return isset($this->fieldMappings[$fieldName]) ?
1779
            $this->fieldMappings[$fieldName]['type'] : null;
1780 5
    }
1781
1782 5
    /**
1783 2
     * {@inheritDoc}
1784
     */
1785
    public function getAssociationTargetClass($assocName) : ?string
1786 3
    {
1787
        if (! isset($this->associationMappings[$assocName])) {
1788
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1789
        }
1790
1791
        return $this->associationMappings[$assocName]['targetDocument'];
1792
    }
1793
1794
    /**
1795
     * Retrieve the collectionClass associated with an association
1796
     */
1797
    public function getAssociationCollectionClass(string $assocName) : string
1798
    {
1799
        if (! isset($this->associationMappings[$assocName])) {
1800
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1801
        }
1802
1803
        if (! array_key_exists('collectionClass', $this->associationMappings[$assocName])) {
1804
            throw new InvalidArgumentException("collectionClass can only be applied to 'embedMany' and 'referenceMany' associations.");
1805
        }
1806
1807
        return $this->associationMappings[$assocName]['collectionClass'];
1808
    }
1809
1810
    /**
1811
     * {@inheritDoc}
1812
     */
1813
    public function isAssociationInverseSide($fieldName) : bool
1814
    {
1815
        throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1816
    }
1817
1818
    /**
1819
     * {@inheritDoc}
1820
     */
1821
    public function getAssociationMappedByTargetField($fieldName)
1822
    {
1823
        throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1824
    }
1825
1826 1635
    /**
1827
     * Map a field.
1828 1635
     *
1829 9
     * @throws MappingException
1830
     */
1831 1635
    public function mapField(array $mapping) : array
1832
    {
1833
        if (! isset($mapping['fieldName']) && isset($mapping['name'])) {
1834 1635
            $mapping['fieldName'] = $mapping['name'];
1835 1634
        }
1836
        if (! isset($mapping['fieldName']) || ! is_string($mapping['fieldName'])) {
1837
            throw MappingException::missingFieldName($this->name);
1838 1635
        }
1839 1
        if (! isset($mapping['name'])) {
1840
            $mapping['name'] = $mapping['fieldName'];
1841 1634
        }
1842 1
1843
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1844 1633
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, (string) $mapping['name']);
1845 134
        }
1846
        if ($this->discriminatorField !== null && $this->discriminatorField === $mapping['name']) {
1847 1633
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1848 134
        }
1849 134
        if (isset($mapping['collectionClass'])) {
1850 1
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1851
        }
1852
        if (! empty($mapping['collectionClass'])) {
1853
            $rColl = new ReflectionClass($mapping['collectionClass']);
1854 1632
            if (! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1855 1
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1856
            }
1857
        }
1858 1631
1859
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1860 1631
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1861 1308
        }
1862
1863
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : [];
1864 1631
1865 1259
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
1866 1625
            $cascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1867 1056
        }
1868
1869
        if (isset($mapping['embedded'])) {
1870 1631
            unset($mapping['cascade']);
1871 1631
        } elseif (isset($mapping['cascade'])) {
1872 1631
            $mapping['cascade'] = $cascades;
1873 1631
        }
1874 1631
1875
        $mapping['isCascadeRemove']  = in_array('remove', $cascades);
1876 1631
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1877 1598
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1878 1598
        $mapping['isCascadeMerge']   = in_array('merge', $cascades);
1879 1598
        $mapping['isCascadeDetach']  = in_array('detach', $cascades);
1880 1586
1881
        if (isset($mapping['id']) && $mapping['id'] === true) {
1882 1598
            $mapping['name']  = '_id';
1883 1598
            $this->identifier = $mapping['fieldName'];
1884 1598
            if (isset($mapping['strategy'])) {
1885 1528
                $this->generatorType = constant(self::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
1886 1528
            }
1887
            $this->generatorOptions = $mapping['options'] ?? [];
1888 230
            switch ($this->generatorType) {
1889 56
                case self::GENERATOR_TYPE_AUTO:
1890 174
                    $mapping['type'] = 'id';
1891 158
                    break;
1892
                default:
1893
                    if (! empty($this->generatorOptions['type'])) {
1894 1598
                        $mapping['type'] = $this->generatorOptions['type'];
1895
                    } elseif (empty($mapping['type'])) {
1896
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
1897 1631
                    }
1898 45
            }
1899
            unset($this->generatorOptions['type']);
1900
        }
1901 1631
1902 1631
        if (! isset($mapping['nullable'])) {
1903 1631
            $mapping['nullable'] = false;
1904 1631
        }
1905
1906 3
        if (isset($mapping['reference'])
1907
            && isset($mapping['storeAs'])
1908
            && $mapping['storeAs'] === self::REFERENCE_STORE_AS_ID
1909 1628
            && ! isset($mapping['targetDocument'])
1910 1628
        ) {
1911 4
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
1912
        }
1913
1914 1624
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
1915 1
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
1916
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
1917
        }
1918 1623
1919 3
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
1920
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
1921
        }
1922 1620
1923 1179
        if (isset($mapping['repositoryMethod']) && ! (empty($mapping['skip']) && empty($mapping['limit']) && empty($mapping['sort']))) {
1924
            throw MappingException::repositoryMethodCanNotBeCombinedWithSkipLimitAndSort($this->name, $mapping['fieldName']);
1925 1620
        }
1926 1112
1927
        if (isset($mapping['reference']) && $mapping['type'] === 'one') {
1928 1620
            $mapping['association'] = self::REFERENCE_ONE;
1929 1110
        }
1930
        if (isset($mapping['reference']) && $mapping['type'] === 'many') {
1931 1620
            $mapping['association'] = self::REFERENCE_MANY;
1932 1148
        }
1933
        if (isset($mapping['embedded']) && $mapping['type'] === 'one') {
1934
            $mapping['association'] = self::EMBED_ONE;
1935 1620
        }
1936 970
        if (isset($mapping['embedded']) && $mapping['type'] === 'many') {
1937
            $mapping['association'] = self::EMBED_MANY;
1938
        }
1939 1620
1940 172
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
1941 172
            $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
1942
        }
1943 1620
1944 25
        if (isset($mapping['version'])) {
1945 25
            $mapping['notSaved'] = true;
1946
            $this->setVersionMapping($mapping);
1947 1620
        }
1948 1620
        if (isset($mapping['lock'])) {
1949 1620
            $mapping['notSaved'] = true;
1950 1246
            $this->setLockMapping($mapping);
1951 273
        }
1952 273
        $mapping['isOwningSide']  = true;
1953
        $mapping['isInverseSide'] = false;
1954 1246
        if (isset($mapping['reference'])) {
1955 952
            if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
1956 952
                $mapping['isOwningSide']  = true;
1957
                $mapping['isInverseSide'] = false;
1958 1246
            }
1959 141
            if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
1960 141
                $mapping['isInverseSide'] = true;
1961
                $mapping['isOwningSide']  = false;
1962 1246
            }
1963 1227
            if (isset($mapping['repositoryMethod'])) {
1964
                $mapping['isInverseSide'] = true;
1965
                $mapping['isOwningSide']  = false;
1966
            }
1967 1620
            if (! isset($mapping['orphanRemoval'])) {
1968
                $mapping['orphanRemoval'] = false;
1969
            }
1970
        }
1971 1620
1972 1
        if (! empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || ! $mapping['isInverseSide'])) {
1973
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
1974
        }
1975 1619
1976 1618
        if ($this->isFile && ! $this->isAllowedGridFSField($mapping['name'])) {
1977
            throw MappingException::fieldNotAllowedForGridFS($this->name, $mapping['fieldName']);
1978 1618
        }
1979 1618
1980 1407
        $this->applyStorageStrategy($mapping);
1981
        $this->checkDuplicateMapping($mapping);
1982
        $this->typeRequirementsAreMet($mapping);
1983 1618
1984 1617
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
1985 1617
        if (isset($mapping['association'])) {
1986
            $this->associationMappings[$mapping['fieldName']] = $mapping;
1987 1617
        }
1988
1989
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
1990
        $reflProp->setAccessible(true);
1991
        $this->reflFields[$mapping['fieldName']] = $reflProp;
1992
1993
        return $mapping;
1994
    }
1995
1996
    /**
1997
     * Determines which fields get serialized.
1998
     *
1999
     * It is only serialized what is necessary for best unserialization performance.
2000
     * That means any metadata properties that are not set or empty or simply have
2001
     * their default value are NOT serialized.
2002
     *
2003 6
     * Parts that are also NOT serialized because they can not be properly unserialized:
2004
     *      - reflClass (ReflectionClass)
2005
     *      - reflFields (ReflectionProperty array)
2006
     *
2007 6
     * @return array The names of all the fields that should be serialized.
2008
     */
2009
    public function __sleep()
2010
    {
2011
        // This metadata is always serialized/cached.
2012
        $serialized = [
2013
            'fieldMappings',
2014
            'associationMappings',
2015
            'identifier',
2016
            'name',
2017
            'db',
2018
            'collection',
2019
            'readPreference',
2020
            'readPreferenceTags',
2021
            'writeConcern',
2022
            'rootDocumentName',
2023
            'generatorType',
2024
            'generatorOptions',
2025 6
            'idGenerator',
2026
            'indexes',
2027
            'shardKey',
2028
        ];
2029 6
2030 1
        // The rest of the metadata is only serialized if necessary.
2031
        if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {
2032
            $serialized[] = 'changeTrackingPolicy';
2033 6
        }
2034 4
2035 4
        if ($this->customRepositoryClassName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customRepositoryClassName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2036 4
            $serialized[] = 'customRepositoryClassName';
2037 4
        }
2038 4
2039 4
        if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE || $this->discriminatorField !== null) {
2040 4
            $serialized[] = 'inheritanceType';
2041
            $serialized[] = 'discriminatorField';
2042
            $serialized[] = 'discriminatorValue';
2043 6
            $serialized[] = 'discriminatorMap';
2044 1
            $serialized[] = 'defaultDiscriminatorValue';
2045
            $serialized[] = 'parentClasses';
2046
            $serialized[] = 'subClasses';
2047 6
        }
2048 1
2049
        if ($this->isMappedSuperclass) {
2050
            $serialized[] = 'isMappedSuperclass';
2051 6
        }
2052
2053
        if ($this->isEmbeddedDocument) {
2054
            $serialized[] = 'isEmbeddedDocument';
2055 6
        }
2056
2057
        if ($this->isQueryResultDocument) {
2058
            $serialized[] = 'isQueryResultDocument';
2059
        }
2060 6
2061
        if ($this->isView()) {
2062
            $serialized[] = 'isView';
2063
            $serialized[] = 'rootClass';
2064
        }
2065
2066 6
        if ($this->isFile) {
2067
            $serialized[] = 'isFile';
2068
            $serialized[] = 'bucketName';
2069
            $serialized[] = 'chunkSizeBytes';
2070
        }
2071 6
2072
        if ($this->isVersioned) {
2073
            $serialized[] = 'isVersioned';
2074
            $serialized[] = 'versionField';
2075 6
        }
2076 1
2077 1
        if ($this->lifecycleCallbacks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->lifecycleCallbacks of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2078 1
            $serialized[] = 'lifecycleCallbacks';
2079
        }
2080
2081 6
        if ($this->collectionCapped) {
2082
            $serialized[] = 'collectionCapped';
2083
            $serialized[] = 'collectionSize';
2084
            $serialized[] = 'collectionMax';
2085 6
        }
2086
2087
        if ($this->isReadOnly) {
2088
            $serialized[] = 'isReadOnly';
2089
        }
2090
2091 6
        return $serialized;
2092
    }
2093
2094 6
    /**
2095 6
     * Restores some state that can not be serialized/unserialized.
2096
     */
2097 6
    public function __wakeup()
2098 3
    {
2099 1
        // Restore ReflectionClass and properties
2100
        $this->reflClass    = new ReflectionClass($this->name);
2101 3
        $this->instantiator = new Instantiator();
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...
2102
2103 3
        foreach ($this->fieldMappings as $field => $mapping) {
2104 3
            if (isset($mapping['declared'])) {
2105
                $reflField = new ReflectionProperty($mapping['declared'], $field);
2106 6
            } else {
2107
                $reflField = $this->reflClass->getProperty($field);
2108
            }
2109
            $reflField->setAccessible(true);
2110
            $this->reflFields[$field] = $reflField;
2111 374
        }
2112
    }
2113 374
2114
    /**
2115
     * Creates a new instance of the mapped class, without invoking the constructor.
2116 146
     */
2117
    public function newInstance() : object
2118 146
    {
2119
        return $this->instantiator->instantiate($this->name);
2120
    }
2121 1618
2122
    private function isAllowedGridFSField(string $name) : bool
2123 1618
    {
2124 982
        return in_array($name, self::ALLOWED_GRIDFS_FIELDS, true);
2125
    }
2126
2127 1618
    private function typeRequirementsAreMet(array $mapping) : void
2128
    {
2129 1564
        if ($mapping['type'] === Type::DECIMAL128 && ! extension_loaded('bcmath')) {
2130 135
            throw MappingException::typeRequirementsNotFulfilled($this->name, $mapping['fieldName'], Type::DECIMAL128, 'ext-bcmath is missing');
2131
        }
2132
    }
2133
2134 1560
    private function checkDuplicateMapping(array $mapping) : void
2135 1560
    {
2136
        if ($mapping['notSaved'] ?? false) {
2137
            return;
2138
        }
2139 2
2140
        foreach ($this->fieldMappings as $fieldName => $otherMapping) {
2141
            // Ignore fields with the same name - we can safely override their mapping
2142
            if ($mapping['fieldName'] === $fieldName) {
2143 2
                continue;
2144
            }
2145 1618
2146
            // Ignore fields with a different name in the database
2147
            if ($mapping['name'] !== $otherMapping['name']) {
2148
                continue;
2149
            }
2150
2151
            // If the other field is not saved, ignore it as well
2152
            if ($otherMapping['notSaved'] ?? false) {
2153
                continue;
2154
            }
2155
2156
            throw MappingException::duplicateDatabaseFieldName($this->getName(), $mapping['fieldName'], $mapping['name'], $fieldName);
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
2157
        }
2158
    }
2159
}
2160