Completed
Push — master ( bc5e59...efdde4 )
by Andreas
24:33 queued 11s
created

ClassMetadata::typeRequirementsAreMet()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

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