Completed
Pull Request — master (#2128)
by Maciej
28:40 queued 22:08
created

ClassMetadata::getAssociationTargetClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
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
     * metadata of the class with the given name.
519
     */
520 1684
    public function __construct(string $documentName)
521
    {
522 1684
        $this->name             = $documentName;
523 1684
        $this->rootDocumentName = $documentName;
524 1684
        $this->reflClass        = new ReflectionClass($documentName);
525 1684
        $this->setCollection($this->reflClass->getShortName());
526 1684
        $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 1684
    }
528
529
    /**
530
     * Helper method to get reference id of ref* type references
531
     *
532
     * @internal
533
     *
534
     * @param mixed $reference
535
     *
536
     * @return mixed
537
     */
538 121
    public static function getReferenceId($reference, string $storeAs)
539
    {
540 121
        return $storeAs === self::REFERENCE_STORE_AS_ID ? $reference : $reference[self::getReferencePrefix($storeAs) . 'id'];
541
    }
542
543
    /**
544
     * Returns the reference prefix used for a reference
545
     */
546 189
    private static function getReferencePrefix(string $storeAs) : string
547
    {
548 189
        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
        }
551
552 189
        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
     * @param string $pathPrefix The field path prefix
561
     */
562 145
    public static function getReferenceFieldName(string $storeAs, string $pathPrefix = '') : string
563
    {
564 145
        if ($storeAs === self::REFERENCE_STORE_AS_ID) {
565 104
            return $pathPrefix;
566
        }
567
568 126
        return ($pathPrefix ? $pathPrefix . '.' : '') . static::getReferencePrefix($storeAs) . 'id';
0 ignored issues
show
Bug introduced by
Since getReferencePrefix() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getReferencePrefix() to at least protected.

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

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

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

}

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

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

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

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

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

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
569
    }
570
571
    /**
572
     * {@inheritDoc}
573
     */
574 1604
    public function getReflectionClass() : ReflectionClass
575
    {
576 1604
        return $this->reflClass;
577
    }
578
579
    /**
580
     * {@inheritDoc}
581
     */
582 366
    public function isIdentifier($fieldName) : bool
583
    {
584 366
        return $this->identifier === $fieldName;
585
    }
586
587
    /**
588
     * Sets the mapped identifier field of this class.
589
     *
590
     * @internal
591
     */
592 1038
    public function setIdentifier(?string $identifier) : void
593
    {
594 1038
        $this->identifier = $identifier;
595 1038
    }
596
597
    /**
598
     * {@inheritDoc}
599
     *
600
     * Since MongoDB only allows exactly one identifier field
601
     * this will always return an array with only one value
602
     */
603 12
    public function getIdentifier() : array
604
    {
605 12
        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
     * return (string|null)[]
613
     */
614 109
    public function getIdentifierFieldNames() : array
615
    {
616 109
        return [$this->identifier];
617
    }
618
619
    /**
620
     * {@inheritDoc}
621
     */
622 1011
    public function hasField($fieldName) : bool
623
    {
624 1011
        return isset($this->fieldMappings[$fieldName]);
625
    }
626
627
    /**
628
     * Sets the inheritance type used by the class and it's subclasses.
629
     */
630 1059
    public function setInheritanceType(int $type) : void
631
    {
632 1059
        $this->inheritanceType = $type;
633 1059
    }
634
635
    /**
636
     * Checks whether a mapped field is inherited from an entity superclass.
637
     */
638 1590
    public function isInheritedField(string $fieldName) : bool
639
    {
640 1590
        return isset($this->fieldMappings[$fieldName]['inherited']);
641
    }
642
643
    /**
644
     * Registers a custom repository class for the document class.
645
     */
646 993
    public function setCustomRepositoryClass(?string $repositoryClassName) : void
647
    {
648 993
        if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
649
            return;
650
        }
651
652 993
        $this->customRepositoryClassName = $repositoryClassName;
653 993
    }
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
     *                                   a Proxy of this class.
661
     */
662 670
    public function invokeLifecycleCallbacks(string $event, object $document, ?array $arguments = null) : void
663
    {
664 670
        if ($this->isView()) {
665
            return;
666
        }
667
668 670
        if (! $document instanceof $this->name) {
669 1
            throw new InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"', $this->name, get_class($document)));
670
        }
671
672 669
        if (empty($this->lifecycleCallbacks[$event])) {
673 654
            return;
674
        }
675
676 193
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
677 193
            if ($arguments !== null) {
678 192
                $document->$callback(...$arguments);
679
            } else {
680 2
                $document->$callback();
681
            }
682
        }
683 193
    }
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
     * If the callback is already registered, this is a NOOP.
705
     */
706 940
    public function addLifecycleCallback(string $callback, string $event) : void
707
    {
708 940
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
709 1
            return;
710
        }
711
712 940
        $this->lifecycleCallbacks[$event][] = $callback;
713 940
    }
714
715
    /**
716
     * Sets the lifecycle callbacks for documents of this class.
717
     *
718
     * Any previously registered callbacks are overwritten.
719
     */
720 1037
    public function setLifecycleCallbacks(array $callbacks) : void
721
    {
722 1037
        $this->lifecycleCallbacks = $callbacks;
723 1037
    }
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
     * @param array|string $fields Database field name(s)
732
     */
733 14
    public function registerAlsoLoadMethod(string $method, $fields) : void
734
    {
735 14
        $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : [$fields];
736 14
    }
737
738
    /**
739
     * Sets the AlsoLoad methods for documents of this class.
740
     *
741
     * Any previously registered methods are overwritten.
742
     */
743 1037
    public function setAlsoLoadMethods(array $methods) : void
744
    {
745 1037
        $this->alsoLoadMethods = $methods;
746 1037
    }
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
     *                          "name" attribute of a mapped field.
759
     */
760 1068
    public function setDiscriminatorField($discriminatorField) : void
761
    {
762 1068
        if ($this->isFile) {
763
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
764
        }
765
766 1068
        if ($discriminatorField === null) {
767 988
            $this->discriminatorField = null;
768
769 988
            return;
770
        }
771
772
        // @todo: deprecate, document and remove this:
773
        // Handle array argument with name/fieldName keys for BC
774 210
        if (is_array($discriminatorField)) {
775
            if (isset($discriminatorField['name'])) {
776
                $discriminatorField = $discriminatorField['name'];
777
            } elseif (isset($discriminatorField['fieldName'])) {
778
                $discriminatorField = $discriminatorField['fieldName'];
779
            }
780
        }
781
782 210
        foreach ($this->fieldMappings as $fieldMapping) {
783 4
            if ($discriminatorField === $fieldMapping['name']) {
784 1
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
785
            }
786
        }
787
788 209
        $this->discriminatorField = $discriminatorField;
789 209
    }
790
791
    /**
792
     * Sets the discriminator values used by this class.
793
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
794
     *
795
     * @throws MappingException
796
     */
797 1057
    public function setDiscriminatorMap(array $map) : void
798
    {
799 1057
        if ($this->isFile) {
800
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
801
        }
802
803 1057
        $this->subClasses         = [];
804 1057
        $this->discriminatorMap   = [];
805 1057
        $this->discriminatorValue = null;
806
807 1057
        foreach ($map as $value => $className) {
808 197
            $this->discriminatorMap[$value] = $className;
809 197
            if ($this->name === $className) {
810 188
                $this->discriminatorValue = $value;
811
            } else {
812 195
                if (! class_exists($className)) {
813
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
814
                }
815 195
                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 180
                    $this->subClasses[] = $className;
817
                }
818
            }
819
        }
820 1057
    }
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
     * @throws MappingException
827
     */
828 1040
    public function setDefaultDiscriminatorValue(?string $defaultDiscriminatorValue) : void
829
    {
830 1040
        if ($this->isFile) {
831
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
832
        }
833
834 1040
        if ($defaultDiscriminatorValue === null) {
835 1037
            $this->defaultDiscriminatorValue = null;
836
837 1037
            return;
838
        }
839
840 129
        if (! array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
841
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
842
        }
843
844 129
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
845 129
    }
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
     * @throws MappingException
853
     */
854 4
    public function setDiscriminatorValue(string $value) : void
855
    {
856 4
        if ($this->isFile) {
857
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
858
        }
859
860 4
        $this->discriminatorMap[$value] = $this->name;
861 4
        $this->discriminatorValue       = $value;
862 4
    }
863
864
    /**
865
     * Add a index for this Document.
866
     */
867 283
    public function addIndex(array $keys, array $options = []) : void
868
    {
869 283
        $this->indexes[] = [
870
            'keys' => array_map(static function ($value) {
871 283
                if ($value === 1 || $value === -1) {
872 126
                    return $value;
873
                }
874 283
                if (is_string($value)) {
875 283
                    $lower = strtolower($value);
876 283
                    if ($lower === 'asc') {
877 276
                        return 1;
878
                    }
879
880 133
                    if ($lower === 'desc') {
881
                        return -1;
882
                    }
883
                }
884
885 133
                return $value;
886 283
            }, $keys),
887 283
            'options' => $options,
888
        ];
889 283
    }
890
891
    /**
892
     * Returns the array of indexes for this Document.
893
     */
894 50
    public function getIndexes() : array
895
    {
896 50
        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
     * @throws MappingException
911
     */
912 162
    public function setShardKey(array $keys, array $options = []) : void
913
    {
914 162
        if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && $this->shardKey !== []) {
915 2
            throw MappingException::shardKeyInSingleCollInheritanceSubclass($this->getName());
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
916
        }
917
918 162
        if ($this->isEmbeddedDocument) {
919 2
            throw MappingException::embeddedDocumentCantHaveShardKey($this->getName());
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
920
        }
921
922 160
        foreach (array_keys($keys) as $field) {
923 160
            if (! isset($this->fieldMappings[$field])) {
924 153
                continue;
925
            }
926
927 132
            if (in_array($this->fieldMappings[$field]['type'], ['many', 'collection'])) {
928 3
                throw MappingException::noMultiKeyShardKeys($this->getName(), $field);
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
929
            }
930
931 129
            if ($this->fieldMappings[$field]['strategy'] !== self::STORAGE_STRATEGY_SET) {
932 1
                throw MappingException::onlySetStrategyAllowedInShardKey($this->getName(), $field);
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
933
            }
934
        }
935
936 156
        $this->shardKey = [
937
            'keys' => array_map(static function ($value) {
938 156
                if ($value === 1 || $value === -1) {
939 5
                    return $value;
940
                }
941 156
                if (is_string($value)) {
942 156
                    $lower = strtolower($value);
943 156
                    if ($lower === 'asc') {
944 154
                        return 1;
945
                    }
946
947 128
                    if ($lower === 'desc') {
948
                        return -1;
949
                    }
950
                }
951
952 128
                return $value;
953 156
            }, $keys),
954 156
            'options' => $options,
955
        ];
956 156
    }
957
958 27
    public function getShardKey() : array
959
    {
960 27
        return $this->shardKey;
961
    }
962
963
    /**
964
     * Checks whether this document has shard key or not.
965
     */
966 1292
    public function isSharded() : bool
967
    {
968 1292
        return $this->shardKey !== [];
969
    }
970
971
    /**
972
     * Sets the read preference used by this class.
973
     */
974 1037
    public function setReadPreference(?string $readPreference, array $tags) : void
975
    {
976 1037
        $this->readPreference     = $readPreference;
977 1037
        $this->readPreferenceTags = $tags;
978 1037
    }
979
980
    /**
981
     * Sets the write concern used by this class.
982
     *
983
     * @param string|int|null $writeConcern
984
     */
985 1047
    public function setWriteConcern($writeConcern) : void
986
    {
987 1047
        $this->writeConcern = $writeConcern;
988 1047
    }
989
990
    /**
991
     * @return int|string|null
992
     */
993 11
    public function getWriteConcern()
994
    {
995 11
        return $this->writeConcern;
996
    }
997
998
    /**
999
     * Whether there is a write concern configured for this class.
1000
     */
1001 618
    public function hasWriteConcern() : bool
1002
    {
1003 618
        return $this->writeConcern !== null;
1004
    }
1005
1006
    /**
1007
     * Sets the change tracking policy used by this class.
1008
     */
1009 1038
    public function setChangeTrackingPolicy(int $policy) : void
1010
    {
1011 1038
        $this->changeTrackingPolicy = $policy;
1012 1038
    }
1013
1014
    /**
1015
     * Whether the change tracking policy of this class is "deferred explicit".
1016
     */
1017 70
    public function isChangeTrackingDeferredExplicit() : bool
1018
    {
1019 70
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT;
1020
    }
1021
1022
    /**
1023
     * Whether the change tracking policy of this class is "deferred implicit".
1024
     */
1025 629
    public function isChangeTrackingDeferredImplicit() : bool
1026
    {
1027 629
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT;
1028
    }
1029
1030
    /**
1031
     * Whether the change tracking policy of this class is "notify".
1032
     */
1033 349
    public function isChangeTrackingNotify() : bool
1034
    {
1035 349
        return $this->changeTrackingPolicy === self::CHANGETRACKING_NOTIFY;
1036
    }
1037
1038
    /**
1039
     * Gets the ReflectionProperties of the mapped class.
1040
     */
1041 1
    public function getReflectionProperties() : array
1042
    {
1043 1
        return $this->reflFields;
1044
    }
1045
1046
    /**
1047
     * Gets a ReflectionProperty for a specific field of the mapped class.
1048
     */
1049 108
    public function getReflectionProperty(string $name) : ReflectionProperty
1050
    {
1051 108
        return $this->reflFields[$name];
1052
    }
1053
1054
    /**
1055
     * {@inheritDoc}
1056
     */
1057 1612
    public function getName() : string
1058
    {
1059 1612
        return $this->name;
1060
    }
1061
1062
    /**
1063
     * Returns the database this Document is mapped to.
1064
     */
1065 1498
    public function getDatabase() : ?string
1066
    {
1067 1498
        return $this->db;
1068
    }
1069
1070
    /**
1071
     * Set the database this Document is mapped to.
1072
     */
1073 179
    public function setDatabase(?string $db) : void
1074
    {
1075 179
        $this->db = $db;
1076 179
    }
1077
1078
    /**
1079
     * Get the collection this Document is mapped to.
1080
     */
1081 1497
    public function getCollection() : string
1082
    {
1083 1497
        return $this->collection;
1084
    }
1085
1086
    /**
1087
     * Sets the collection this Document is mapped to.
1088
     *
1089
     * @param array|string $name
1090
     *
1091
     * @throws InvalidArgumentException
1092
     */
1093 1684
    public function setCollection($name) : void
1094
    {
1095 1684
        if (is_array($name)) {
1096 1
            if (! isset($name['name'])) {
1097
                throw new InvalidArgumentException('A name key is required when passing an array to setCollection()');
1098
            }
1099 1
            $this->collectionCapped = $name['capped'] ?? false;
1100 1
            $this->collectionSize   = $name['size'] ?? 0;
1101 1
            $this->collectionMax    = $name['max'] ?? 0;
1102 1
            $this->collection       = $name['name'];
1103
        } else {
1104 1684
            $this->collection = $name;
1105
        }
1106 1684
    }
1107
1108 137
    public function getBucketName() : ?string
1109
    {
1110 137
        return $this->bucketName;
1111
    }
1112
1113 1
    public function setBucketName(string $bucketName) : void
1114
    {
1115 1
        $this->bucketName = $bucketName;
1116 1
        $this->setCollection($bucketName . '.files');
1117 1
    }
1118
1119 12
    public function getChunkSizeBytes() : ?int
1120
    {
1121 12
        return $this->chunkSizeBytes;
1122
    }
1123
1124 140
    public function setChunkSizeBytes(int $chunkSizeBytes) : void
1125
    {
1126 140
        $this->chunkSizeBytes = $chunkSizeBytes;
1127 140
    }
1128
1129
    /**
1130
     * Get whether or not the documents collection is capped.
1131
     */
1132 11
    public function getCollectionCapped() : bool
1133
    {
1134 11
        return $this->collectionCapped;
1135
    }
1136
1137
    /**
1138
     * Set whether or not the documents collection is capped.
1139
     */
1140 1
    public function setCollectionCapped(bool $bool) : void
1141
    {
1142 1
        $this->collectionCapped = $bool;
1143 1
    }
1144
1145
    /**
1146
     * Get the collection size
1147
     */
1148 11
    public function getCollectionSize() : ?int
1149
    {
1150 11
        return $this->collectionSize;
1151
    }
1152
1153
    /**
1154
     * Set the collection size.
1155
     */
1156 1
    public function setCollectionSize(int $size) : void
1157
    {
1158 1
        $this->collectionSize = $size;
1159 1
    }
1160
1161
    /**
1162
     * Get the collection max.
1163
     */
1164 11
    public function getCollectionMax() : ?int
1165
    {
1166 11
        return $this->collectionMax;
1167
    }
1168
1169
    /**
1170
     * Set the collection max.
1171
     */
1172 1
    public function setCollectionMax(int $max) : void
1173
    {
1174 1
        $this->collectionMax = $max;
1175 1
    }
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
     * @throws MappingException
1189
     */
1190 1622
    private function applyStorageStrategy(array &$mapping) : void
1191
    {
1192 1622
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1193 1601
            return;
1194
        }
1195
1196
        switch (true) {
1197 1582
            case $mapping['type'] === 'many':
1198 1255
                $defaultStrategy   = CollectionHelper::DEFAULT_STRATEGY;
1199
                $allowedStrategies = [
1200 1255
                    self::STORAGE_STRATEGY_PUSH_ALL,
1201 1255
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1202 1255
                    self::STORAGE_STRATEGY_SET,
1203 1255
                    self::STORAGE_STRATEGY_SET_ARRAY,
1204 1255
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1205 1255
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1206
                ];
1207 1255
                break;
1208
1209 1570
            case $mapping['type'] === 'one':
1210 1270
                $defaultStrategy   = self::STORAGE_STRATEGY_SET;
1211 1270
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1212 1270
                break;
1213
1214
            default:
1215 1529
                $defaultStrategy   = self::STORAGE_STRATEGY_SET;
1216 1529
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1217 1529
                $type              = Type::getType($mapping['type']);
1218 1529
                if ($type instanceof Incrementable) {
1219 996
                    $allowedStrategies[] = self::STORAGE_STRATEGY_INCREMENT;
1220
                }
1221
        }
1222
1223 1582
        if (! isset($mapping['strategy'])) {
1224 1574
            $mapping['strategy'] = $defaultStrategy;
1225
        }
1226
1227 1582
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1228
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1229
        }
1230
1231 1582
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1232 1582
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1233 1
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1234
        }
1235 1581
    }
1236
1237
    /**
1238
     * Map a single embedded document.
1239
     */
1240 6
    public function mapOneEmbedded(array $mapping) : void
1241
    {
1242 6
        $mapping['embedded'] = true;
1243 6
        $mapping['type']     = 'one';
1244 6
        $this->mapField($mapping);
1245 5
    }
1246
1247
    /**
1248
     * Map a collection of embedded documents.
1249
     */
1250 6
    public function mapManyEmbedded(array $mapping) : void
1251
    {
1252 6
        $mapping['embedded'] = true;
1253 6
        $mapping['type']     = 'many';
1254 6
        $this->mapField($mapping);
1255 6
    }
1256
1257
    /**
1258
     * Map a single document reference.
1259
     */
1260 3
    public function mapOneReference(array $mapping) : void
1261
    {
1262 3
        $mapping['reference'] = true;
1263 3
        $mapping['type']      = 'one';
1264 3
        $this->mapField($mapping);
1265 3
    }
1266
1267
    /**
1268
     * Map a collection of document references.
1269
     */
1270 1
    public function mapManyReference(array $mapping) : void
1271
    {
1272 1
        $mapping['reference'] = true;
1273 1
        $mapping['type']      = 'many';
1274 1
        $this->mapField($mapping);
1275 1
    }
1276
1277
    /**
1278
     * Adds a field mapping without completing/validating it.
1279
     * This is mainly used to add inherited field mappings to derived classes.
1280
     *
1281
     * @internal
1282
     */
1283 205
    public function addInheritedFieldMapping(array $fieldMapping) : void
1284
    {
1285 205
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1286
1287 205
        if (! isset($fieldMapping['association'])) {
1288 205
            return;
1289
        }
1290
1291 153
        $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1292 153
    }
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
     *
1298
     * @internal
1299
     *
1300
     * @throws MappingException
1301
     */
1302 154
    public function addInheritedAssociationMapping(array $mapping) : void
1303
    {
1304 154
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1305 154
    }
1306
1307
    /**
1308
     * Checks whether the class has a mapped association with the given field name.
1309
     */
1310 33
    public function hasReference(string $fieldName) : bool
1311
    {
1312 33
        return isset($this->fieldMappings[$fieldName]['reference']);
1313
    }
1314
1315
    /**
1316
     * Checks whether the class has a mapped embed with the given field name.
1317
     */
1318 4
    public function hasEmbed(string $fieldName) : bool
1319
    {
1320 4
        return isset($this->fieldMappings[$fieldName]['embedded']);
1321
    }
1322
1323
    /**
1324
     * {@inheritDoc}
1325
     *
1326
     * Checks whether the class has a mapped association (embed or reference) with the given field name.
1327
     */
1328 6
    public function hasAssociation($fieldName) : bool
1329
    {
1330 6
        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
1355
    /**
1356
     * Checks whether the class has a mapped association for the specified field
1357
     * and if yes, checks whether it is a single-valued association (to-one).
1358
     */
1359 1
    public function isSingleValuedReference(string $fieldName) : bool
1360
    {
1361 1
        return isset($this->fieldMappings[$fieldName]['association']) &&
1362 1
            $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
    }
1394
1395
    /**
1396
     * Sets the ID generator used to generate IDs for instances of this class.
1397
     */
1398 1546
    public function setIdGenerator(AbstractIdGenerator $generator) : void
1399
    {
1400 1546
        $this->idGenerator = $generator;
1401 1546
    }
1402
1403
    /**
1404
     * Casts the identifier to its portable PHP type.
1405
     *
1406
     * @param mixed $id
1407
     *
1408
     * @return mixed $id
1409
     */
1410 669
    public function getPHPIdentifierValue($id)
1411
    {
1412 669
        $idType = $this->fieldMappings[$this->identifier]['type'];
1413
1414 669
        return Type::getType($idType)->convertToPHPValue($id);
1415
    }
1416
1417
    /**
1418
     * Casts the identifier to its database type.
1419
     *
1420
     * @param mixed $id
1421
     *
1422
     * @return mixed $id
1423
     */
1424 740
    public function getDatabaseIdentifierValue($id)
1425
    {
1426 740
        $idType = $this->fieldMappings[$this->identifier]['type'];
1427
1428 740
        return Type::getType($idType)->convertToDatabaseValue($id);
1429
    }
1430
1431
    /**
1432
     * Sets the document identifier of a document.
1433
     *
1434
     * The value will be converted to a PHP type before being set.
1435
     *
1436
     * @param mixed $id
1437
     */
1438 599
    public function setIdentifierValue(object $document, $id) : void
1439
    {
1440 599
        $id = $this->getPHPIdentifierValue($id);
1441 599
        $this->reflFields[$this->identifier]->setValue($document, $id);
1442 599
    }
1443
1444
    /**
1445
     * Gets the document identifier as a PHP type.
1446
     *
1447
     * @return mixed $id
1448
     */
1449 678
    public function getIdentifierValue(object $document)
1450
    {
1451 678
        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
    /**
1467
     * Get the document identifier object as a database type.
1468
     *
1469
     * @return mixed $id
1470
     */
1471 31
    public function getIdentifierObject(object $document)
1472
    {
1473 31
        return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));
1474
    }
1475
1476
    /**
1477
     * Sets the specified field to the specified value on the given document.
1478
     *
1479
     * @param mixed $value
1480
     */
1481 8
    public function setFieldValue(object $document, string $field, $value) : void
1482
    {
1483 8
        if ($document instanceof GhostObjectInterface && ! $document->isProxyInitialized()) {
0 ignored issues
show
Bug introduced by
The class ProxyManager\Proxy\GhostObjectInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
1484
            //property changes to an uninitialized proxy will not be tracked or persisted,
1485
            //so the proxy needs to be loaded first.
1486 1
            $document->initializeProxy();
1487
        }
1488
1489 8
        $this->reflFields[$field]->setValue($document, $value);
1490 8
    }
1491
1492
    /**
1493
     * Gets the specified field's value off the given document.
1494
     *
1495
     * @return mixed
1496
     */
1497 33
    public function getFieldValue(object $document, string $field)
1498
    {
1499 33
        if ($document instanceof GhostObjectInterface && $field !== $this->identifier && ! $document->isProxyInitialized()) {
0 ignored issues
show
Bug introduced by
The class ProxyManager\Proxy\GhostObjectInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
1500 1
            $document->initializeProxy();
1501
        }
1502
1503 33
        return $this->reflFields[$field]->getValue($document);
1504
    }
1505
1506
    /**
1507
     * Gets the mapping of a field.
1508
     *
1509
     * @throws MappingException If the $fieldName is not found in the fieldMappings array.
1510
     */
1511 201
    public function getFieldMapping(string $fieldName) : array
1512
    {
1513 201
        if (! isset($this->fieldMappings[$fieldName])) {
1514 6
            throw MappingException::mappingNotFound($this->name, $fieldName);
1515
        }
1516
1517 199
        return $this->fieldMappings[$fieldName];
1518
    }
1519
1520
    /**
1521
     * Gets mappings of fields holding embedded document(s).
1522
     */
1523 607
    public function getEmbeddedFieldsMappings() : array
1524
    {
1525 607
        return array_filter(
1526 607
            $this->associationMappings,
1527
            static function ($assoc) {
1528 467
                return ! empty($assoc['embedded']);
1529 607
            }
1530
        );
1531
    }
1532
1533
    /**
1534
     * Gets the field mapping by its DB name.
1535
     * E.g. it returns identifier's mapping when called with _id.
1536
     *
1537
     * @throws MappingException
1538
     */
1539 17
    public function getFieldMappingByDbFieldName(string $dbFieldName) : array
1540
    {
1541 17
        foreach ($this->fieldMappings as $mapping) {
1542 17
            if ($mapping['name'] === $dbFieldName) {
1543 15
                return $mapping;
1544
            }
1545
        }
1546
1547 2
        throw MappingException::mappingNotFoundByDbName($this->name, $dbFieldName);
1548
    }
1549
1550
    /**
1551
     * Check if the field is not null.
1552
     */
1553 1
    public function isNullable(string $fieldName) : bool
1554
    {
1555 1
        $mapping = $this->getFieldMapping($fieldName);
1556
1557 1
        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
    }
1567
1568
    /**
1569
     * Sets the type of Id generator to use for the mapped class.
1570
     */
1571 1037
    public function setIdGeneratorType(int $generatorType) : void
1572
    {
1573 1037
        $this->generatorType = $generatorType;
1574 1037
    }
1575
1576
    /**
1577
     * Sets the Id generator options.
1578
     */
1579
    public function setIdGeneratorOptions(array $generatorOptions) : void
1580
    {
1581
        $this->generatorOptions = $generatorOptions;
1582
    }
1583
1584 634
    public function isInheritanceTypeNone() : bool
1585
    {
1586 634
        return $this->inheritanceType === self::INHERITANCE_TYPE_NONE;
1587
    }
1588
1589
    /**
1590
     * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.
1591
     */
1592 1034
    public function isInheritanceTypeSingleCollection() : bool
1593
    {
1594 1034
        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
    /**
1606
     * Sets the mapped subclasses of this class.
1607
     *
1608
     * @param string[] $subclasses The names of all mapped subclasses.
1609
     */
1610 2
    public function setSubclasses(array $subclasses) : void
1611
    {
1612 2
        foreach ($subclasses as $subclass) {
1613 2
            $this->subClasses[] = $subclass;
1614
        }
1615 2
    }
1616
1617
    /**
1618
     * Sets the parent class names.
1619
     * Assumes that the class names in the passed array are in the order:
1620
     * directParent -> directParentParent -> directParentParentParent ... -> root.
1621
     *
1622
     * @param string[] $classNames
1623
     */
1624 1599
    public function setParentClasses(array $classNames) : void
1625
    {
1626 1599
        $this->parentClasses = $classNames;
1627
1628 1599
        if (count($classNames) <= 0) {
1629 1598
            return;
1630
        }
1631
1632 187
        $this->rootDocumentName = (string) array_pop($classNames);
1633 187
    }
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
     * Sets the version field mapping used for versioning. Sets the default
1669
     * value to use depending on the column type.
1670
     *
1671
     * @throws LockException
1672
     */
1673 175
    public function setVersionMapping(array &$mapping) : void
1674
    {
1675 175
        if (! in_array($mapping['type'], [Type::INT, Type::INTEGER, Type::DATE, Type::DATE_IMMUTABLE, Type::DECIMAL128], true)) {
1676 1
            throw LockException::invalidVersionFieldType($mapping['type']);
1677
        }
1678
1679 174
        $this->isVersioned  = true;
1680 174
        $this->versionField = $mapping['fieldName'];
1681 174
    }
1682
1683
    /**
1684
     * Sets whether this class is to be versioned for optimistic locking.
1685
     */
1686 1037
    public function setVersioned(bool $bool) : void
1687
    {
1688 1037
        $this->isVersioned = $bool;
1689 1037
    }
1690
1691
    /**
1692
     * Sets the name of the field that is to be used for versioning if this class is
1693
     * versioned for optimistic locking.
1694
     */
1695 1037
    public function setVersionField(?string $versionField) : void
1696
    {
1697 1037
        $this->versionField = $versionField;
1698 1037
    }
1699
1700
    /**
1701
     * Sets the version field mapping used for versioning. Sets the default
1702
     * value to use depending on the column type.
1703
     *
1704
     * @throws LockException
1705
     */
1706 28
    public function setLockMapping(array &$mapping) : void
1707
    {
1708 28
        if ($mapping['type'] !== 'int') {
1709 1
            throw LockException::invalidLockFieldType($mapping['type']);
1710
        }
1711
1712 27
        $this->isLockable = true;
1713 27
        $this->lockField  = $mapping['fieldName'];
1714 27
    }
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
    }
1732
1733
    /**
1734
     * Marks this class as read only, no change tracking is applied to it.
1735
     */
1736 5
    public function markReadOnly() : void
1737
    {
1738 5
        $this->isReadOnly = true;
1739 5
    }
1740
1741 11
    public function getRootClass() : ?string
1742
    {
1743 11
        return $this->rootClass;
1744
    }
1745
1746 1594
    public function isView() : bool
1747
    {
1748 1594
        return $this->isView;
1749
    }
1750
1751 133
    public function markViewOf(string $rootClass) : void
1752
    {
1753 133
        $this->isView    = true;
1754 133
        $this->rootClass = $rootClass;
1755 133
    }
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
    }
1781
1782
    /**
1783
     * {@inheritDoc}
1784
     */
1785 5
    public function getAssociationTargetClass($assocName) : ?string
1786
    {
1787 5
        if (! isset($this->associationMappings[$assocName])) {
1788 2
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1789
        }
1790
1791 3
        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
    /**
1827
     * Map a field.
1828
     *
1829
     * @throws MappingException
1830
     */
1831 1638
    public function mapField(array $mapping) : array
1832
    {
1833 1638
        if (! isset($mapping['fieldName']) && isset($mapping['name'])) {
1834 9
            $mapping['fieldName'] = $mapping['name'];
1835
        }
1836 1638
        if (! isset($mapping['fieldName']) || ! is_string($mapping['fieldName'])) {
1837
            throw MappingException::missingFieldName($this->name);
1838
        }
1839 1638
        if (! isset($mapping['name'])) {
1840 1637
            $mapping['name'] = $mapping['fieldName'];
1841
        }
1842
1843 1638
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1844 1
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, (string) $mapping['name']);
1845
        }
1846 1637
        if ($this->discriminatorField !== null && $this->discriminatorField === $mapping['name']) {
1847 1
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1848
        }
1849 1636
        if (isset($mapping['collectionClass'])) {
1850 134
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1851
        }
1852 1636
        if (! empty($mapping['collectionClass'])) {
1853 134
            $rColl = new ReflectionClass($mapping['collectionClass']);
1854 134
            if (! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1855 1
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1856
            }
1857
        }
1858
1859 1635
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1860 1
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1861
        }
1862
1863 1634
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : [];
1864
1865 1634
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
1866 1311
            $cascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1867
        }
1868
1869 1634
        if (isset($mapping['embedded'])) {
1870 1262
            unset($mapping['cascade']);
1871 1628
        } elseif (isset($mapping['cascade'])) {
1872 1056
            $mapping['cascade'] = $cascades;
1873
        }
1874
1875 1634
        $mapping['isCascadeRemove']  = in_array('remove', $cascades);
1876 1634
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1877 1634
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1878 1634
        $mapping['isCascadeMerge']   = in_array('merge', $cascades);
1879 1634
        $mapping['isCascadeDetach']  = in_array('detach', $cascades);
1880
1881 1634
        if (isset($mapping['id']) && $mapping['id'] === true) {
1882 1601
            $mapping['name']  = '_id';
1883 1601
            $this->identifier = $mapping['fieldName'];
1884 1601
            if (isset($mapping['strategy'])) {
1885 1589
                $this->generatorType = constant(self::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
1886
            }
1887 1601
            $this->generatorOptions = $mapping['options'] ?? [];
1888 1601
            switch ($this->generatorType) {
1889 1601
                case self::GENERATOR_TYPE_AUTO:
1890 1531
                    $mapping['type'] = 'id';
1891 1531
                    break;
1892
                default:
1893 230
                    if (! empty($this->generatorOptions['type'])) {
1894 56
                        $mapping['type'] = $this->generatorOptions['type'];
1895 174
                    } elseif (empty($mapping['type'])) {
1896 158
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
1897
                    }
1898
            }
1899 1601
            unset($this->generatorOptions['type']);
1900
        }
1901
1902 1634
        if (! isset($mapping['nullable'])) {
1903 45
            $mapping['nullable'] = false;
1904
        }
1905
1906 1634
        if (isset($mapping['reference'])
1907 1634
            && isset($mapping['storeAs'])
1908 1634
            && $mapping['storeAs'] === self::REFERENCE_STORE_AS_ID
1909 1634
            && ! isset($mapping['targetDocument'])
1910
        ) {
1911 3
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
1912
        }
1913
1914 1631
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
1915 1631
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
1916 4
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
1917
        }
1918
1919 1627
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
1920 1
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
1921
        }
1922
1923 1626
        if (isset($mapping['repositoryMethod']) && ! (empty($mapping['skip']) && empty($mapping['limit']) && empty($mapping['sort']))) {
1924 3
            throw MappingException::repositoryMethodCanNotBeCombinedWithSkipLimitAndSort($this->name, $mapping['fieldName']);
1925
        }
1926
1927 1623
        if (isset($mapping['reference']) && $mapping['type'] === 'one') {
1928 1179
            $mapping['association'] = self::REFERENCE_ONE;
1929
        }
1930 1623
        if (isset($mapping['reference']) && $mapping['type'] === 'many') {
1931 1112
            $mapping['association'] = self::REFERENCE_MANY;
1932
        }
1933 1623
        if (isset($mapping['embedded']) && $mapping['type'] === 'one') {
1934 1110
            $mapping['association'] = self::EMBED_ONE;
1935
        }
1936 1623
        if (isset($mapping['embedded']) && $mapping['type'] === 'many') {
1937 1151
            $mapping['association'] = self::EMBED_MANY;
1938
        }
1939
1940 1623
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
1941 970
            $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
1942
        }
1943
1944 1623
        if (isset($mapping['version'])) {
1945 175
            $mapping['notSaved'] = true;
1946 175
            $this->setVersionMapping($mapping);
1947
        }
1948 1623
        if (isset($mapping['lock'])) {
1949 28
            $mapping['notSaved'] = true;
1950 28
            $this->setLockMapping($mapping);
1951
        }
1952 1623
        $mapping['isOwningSide']  = true;
1953 1623
        $mapping['isInverseSide'] = false;
1954 1623
        if (isset($mapping['reference'])) {
1955 1246
            if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
1956 273
                $mapping['isOwningSide']  = true;
1957 273
                $mapping['isInverseSide'] = false;
1958
            }
1959 1246
            if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
1960 952
                $mapping['isInverseSide'] = true;
1961 952
                $mapping['isOwningSide']  = false;
1962
            }
1963 1246
            if (isset($mapping['repositoryMethod'])) {
1964 141
                $mapping['isInverseSide'] = true;
1965 141
                $mapping['isOwningSide']  = false;
1966
            }
1967 1246
            if (! isset($mapping['orphanRemoval'])) {
1968 1227
                $mapping['orphanRemoval'] = false;
1969
            }
1970
        }
1971
1972 1623
        if (! empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || ! $mapping['isInverseSide'])) {
1973
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
1974
        }
1975
1976 1623
        if ($this->isFile && ! $this->isAllowedGridFSField($mapping['name'])) {
1977 1
            throw MappingException::fieldNotAllowedForGridFS($this->name, $mapping['fieldName']);
1978
        }
1979
1980 1622
        $this->applyStorageStrategy($mapping);
1981 1621
        $this->checkDuplicateMapping($mapping);
1982 1621
        $this->typeRequirementsAreMet($mapping);
1983
1984 1621
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
1985 1621
        if (isset($mapping['association'])) {
1986 1410
            $this->associationMappings[$mapping['fieldName']] = $mapping;
1987
        }
1988
1989 1621
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
1990 1620
        $reflProp->setAccessible(true);
1991 1620
        $this->reflFields[$mapping['fieldName']] = $reflProp;
1992
1993 1620
        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
     * Parts that are also NOT serialized because they can not be properly unserialized:
2004
     *      - reflClass (ReflectionClass)
2005
     *      - reflFields (ReflectionProperty array)
2006
     *
2007
     * @return array The names of all the fields that should be serialized.
2008
     */
2009 6
    public function __sleep()
2010
    {
2011
        // This metadata is always serialized/cached.
2012
        $serialized = [
2013 6
            'fieldMappings',
2014
            'associationMappings',
2015
            'identifier',
2016
            'name',
2017
            'db',
2018
            'collection',
2019
            'readPreference',
2020
            'readPreferenceTags',
2021
            'writeConcern',
2022
            'rootDocumentName',
2023
            'generatorType',
2024
            'generatorOptions',
2025
            'idGenerator',
2026
            'indexes',
2027
            'shardKey',
2028
        ];
2029
2030
        // The rest of the metadata is only serialized if necessary.
2031 6
        if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {
2032
            $serialized[] = 'changeTrackingPolicy';
2033
        }
2034
2035 6
        if ($this->customRepositoryClassName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customRepositoryClassName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

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

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

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

Loading history...
2078
            $serialized[] = 'lifecycleCallbacks';
2079
        }
2080
2081 6
        if ($this->collectionCapped) {
2082 1
            $serialized[] = 'collectionCapped';
2083 1
            $serialized[] = 'collectionSize';
2084 1
            $serialized[] = 'collectionMax';
2085
        }
2086
2087 6
        if ($this->isReadOnly) {
2088
            $serialized[] = 'isReadOnly';
2089
        }
2090
2091 6
        return $serialized;
2092
    }
2093
2094
    /**
2095
     * Restores some state that can not be serialized/unserialized.
2096
     */
2097 6
    public function __wakeup()
2098
    {
2099
        // Restore ReflectionClass and properties
2100 6
        $this->reflClass    = new ReflectionClass($this->name);
2101 6
        $this->instantiator = new Instantiator();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Doctrine\Instantiator\Instantiator() of type object<Doctrine\Instantiator\Instantiator> is incompatible with the declared type object<Doctrine\Instanti...\InstantiatorInterface> of property $instantiator.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2102
2103 6
        foreach ($this->fieldMappings as $field => $mapping) {
2104 3
            if (isset($mapping['declared'])) {
2105 1
                $reflField = new ReflectionProperty($mapping['declared'], $field);
2106
            } else {
2107 3
                $reflField = $this->reflClass->getProperty($field);
2108
            }
2109 3
            $reflField->setAccessible(true);
2110 3
            $this->reflFields[$field] = $reflField;
2111
        }
2112 6
    }
2113
2114
    /**
2115
     * Creates a new instance of the mapped class, without invoking the constructor.
2116
     */
2117 374
    public function newInstance() : object
2118
    {
2119 374
        return $this->instantiator->instantiate($this->name);
2120
    }
2121
2122 146
    private function isAllowedGridFSField(string $name) : bool
2123
    {
2124 146
        return in_array($name, self::ALLOWED_GRIDFS_FIELDS, true);
2125
    }
2126
2127 1621
    private function typeRequirementsAreMet(array $mapping) : void
2128
    {
2129 1621
        if ($mapping['type'] === Type::DECIMAL128 && ! extension_loaded('bcmath')) {
2130
            throw MappingException::typeRequirementsNotFulfilled($this->name, $mapping['fieldName'], Type::DECIMAL128, 'ext-bcmath is missing');
2131
        }
2132 1621
    }
2133
2134 1621
    private function checkDuplicateMapping(array $mapping) : void
2135
    {
2136 1621
        if ($mapping['notSaved'] ?? false) {
2137 985
            return;
2138
        }
2139
2140 1621
        foreach ($this->fieldMappings as $fieldName => $otherMapping) {
2141
            // Ignore fields with the same name - we can safely override their mapping
2142 1567
            if ($mapping['fieldName'] === $fieldName) {
2143 135
                continue;
2144
            }
2145
2146
            // Ignore fields with a different name in the database
2147 1563
            if ($mapping['name'] !== $otherMapping['name']) {
2148 1563
                continue;
2149
            }
2150
2151
            // If the other field is not saved, ignore it as well
2152 2
            if ($otherMapping['notSaved'] ?? false) {
2153
                continue;
2154
            }
2155
2156 2
            throw MappingException::duplicateDatabaseFieldName($this->getName(), $mapping['fieldName'], $mapping['name'], $fieldName);
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
2157
        }
2158 1621
    }
2159
}
2160