ClassMetadata::__sleep()   F
last analyzed

Complexity

Conditions 14
Paths 4096

Size

Total Lines 84

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 18.4

Importance

Changes 0
Metric Value
dl 0
loc 84
ccs 28
cts 39
cp 0.7179
rs 2.029
c 0
b 0
f 0
cc 14
nc 4096
nop 0
crap 18.4

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
1990
            Type::INTEGER => Type::INT,
0 ignored issues
show
Deprecated Code introduced by
The constant Doctrine\ODM\MongoDB\Types\Type::INTEGER has been deprecated with message: const was deprecated in doctrine/mongodb-odm 2.1 and will be removed in 3.0. Use Type::INT instead

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
1991
        ];
1992 1621
        if (isset($deprecatedTypes[$mapping['type']])) {
1993 3
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1994 3
                '"%s" type was deprecated in doctrine/mongodb-odm 2.1 and will be removed in 3.0. Use "%s" instead.',
1995 3
                $mapping['type'],
1996 3
                $deprecatedTypes[$mapping['type']]
1997
            ));
1998
        }
1999
2000 1621
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
2001 1621
        if (isset($mapping['association'])) {
2002 1410
            $this->associationMappings[$mapping['fieldName']] = $mapping;
2003
        }
2004
2005 1621
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
2006 1620
        $reflProp->setAccessible(true);
2007 1620
        $this->reflFields[$mapping['fieldName']] = $reflProp;
2008
2009 1620
        return $mapping;
2010
    }
2011
2012
    /**
2013
     * Determines which fields get serialized.
2014
     *
2015
     * It is only serialized what is necessary for best unserialization performance.
2016
     * That means any metadata properties that are not set or empty or simply have
2017
     * their default value are NOT serialized.
2018
     *
2019
     * Parts that are also NOT serialized because they can not be properly unserialized:
2020
     *      - reflClass (ReflectionClass)
2021
     *      - reflFields (ReflectionProperty array)
2022
     *
2023
     * @return array The names of all the fields that should be serialized.
2024
     */
2025 6
    public function __sleep()
2026
    {
2027
        // This metadata is always serialized/cached.
2028
        $serialized = [
2029 6
            'fieldMappings',
2030
            'associationMappings',
2031
            'identifier',
2032
            'name',
2033
            'db',
2034
            'collection',
2035
            'readPreference',
2036
            'readPreferenceTags',
2037
            'writeConcern',
2038
            'rootDocumentName',
2039
            'generatorType',
2040
            'generatorOptions',
2041
            'idGenerator',
2042
            'indexes',
2043
            'shardKey',
2044
        ];
2045
2046
        // The rest of the metadata is only serialized if necessary.
2047 6
        if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {
2048
            $serialized[] = 'changeTrackingPolicy';
2049
        }
2050
2051 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...
2052 1
            $serialized[] = 'customRepositoryClassName';
2053
        }
2054
2055 6
        if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE || $this->discriminatorField !== null) {
2056 4
            $serialized[] = 'inheritanceType';
2057 4
            $serialized[] = 'discriminatorField';
2058 4
            $serialized[] = 'discriminatorValue';
2059 4
            $serialized[] = 'discriminatorMap';
2060 4
            $serialized[] = 'defaultDiscriminatorValue';
2061 4
            $serialized[] = 'parentClasses';
2062 4
            $serialized[] = 'subClasses';
2063
        }
2064
2065 6
        if ($this->isMappedSuperclass) {
2066 1
            $serialized[] = 'isMappedSuperclass';
2067
        }
2068
2069 6
        if ($this->isEmbeddedDocument) {
2070 1
            $serialized[] = 'isEmbeddedDocument';
2071
        }
2072
2073 6
        if ($this->isQueryResultDocument) {
2074
            $serialized[] = 'isQueryResultDocument';
2075
        }
2076
2077 6
        if ($this->isView()) {
2078
            $serialized[] = 'isView';
2079
            $serialized[] = 'rootClass';
2080
        }
2081
2082 6
        if ($this->isFile) {
2083
            $serialized[] = 'isFile';
2084
            $serialized[] = 'bucketName';
2085
            $serialized[] = 'chunkSizeBytes';
2086
        }
2087
2088 6
        if ($this->isVersioned) {
2089
            $serialized[] = 'isVersioned';
2090
            $serialized[] = 'versionField';
2091
        }
2092
2093 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...
2094
            $serialized[] = 'lifecycleCallbacks';
2095
        }
2096
2097 6
        if ($this->collectionCapped) {
2098 1
            $serialized[] = 'collectionCapped';
2099 1
            $serialized[] = 'collectionSize';
2100 1
            $serialized[] = 'collectionMax';
2101
        }
2102
2103 6
        if ($this->isReadOnly) {
2104
            $serialized[] = 'isReadOnly';
2105
        }
2106
2107 6
        return $serialized;
2108
    }
2109
2110
    /**
2111
     * Restores some state that can not be serialized/unserialized.
2112
     */
2113 6
    public function __wakeup()
2114
    {
2115
        // Restore ReflectionClass and properties
2116 6
        $this->reflClass    = new ReflectionClass($this->name);
2117 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...
2118
2119 6
        foreach ($this->fieldMappings as $field => $mapping) {
2120 3
            if (isset($mapping['declared'])) {
2121 1
                $reflField = new ReflectionProperty($mapping['declared'], $field);
2122
            } else {
2123 3
                $reflField = $this->reflClass->getProperty($field);
2124
            }
2125 3
            $reflField->setAccessible(true);
2126 3
            $this->reflFields[$field] = $reflField;
2127
        }
2128 6
    }
2129
2130
    /**
2131
     * Creates a new instance of the mapped class, without invoking the constructor.
2132
     */
2133 374
    public function newInstance() : object
2134
    {
2135 374
        return $this->instantiator->instantiate($this->name);
2136
    }
2137
2138 146
    private function isAllowedGridFSField(string $name) : bool
2139
    {
2140 146
        return in_array($name, self::ALLOWED_GRIDFS_FIELDS, true);
2141
    }
2142
2143 1621
    private function typeRequirementsAreMet(array $mapping) : void
2144
    {
2145 1621
        if ($mapping['type'] === Type::DECIMAL128 && ! extension_loaded('bcmath')) {
2146
            throw MappingException::typeRequirementsNotFulfilled($this->name, $mapping['fieldName'], Type::DECIMAL128, 'ext-bcmath is missing');
2147
        }
2148 1621
    }
2149
2150 1621
    private function checkDuplicateMapping(array $mapping) : void
2151
    {
2152 1621
        if ($mapping['notSaved'] ?? false) {
2153 985
            return;
2154
        }
2155
2156 1621
        foreach ($this->fieldMappings as $fieldName => $otherMapping) {
2157
            // Ignore fields with the same name - we can safely override their mapping
2158 1567
            if ($mapping['fieldName'] === $fieldName) {
2159 135
                continue;
2160
            }
2161
2162
            // Ignore fields with a different name in the database
2163 1563
            if ($mapping['name'] !== $otherMapping['name']) {
2164 1563
                continue;
2165
            }
2166
2167
            // If the other field is not saved, ignore it as well
2168 2
            if ($otherMapping['notSaved'] ?? false) {
2169
                continue;
2170
            }
2171
2172 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...
2173
        }
2174 1621
    }
2175
}
2176