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

ClassMetadata::applyStorageStrategy()   C

Complexity

Conditions 13
Paths 25

Size

Total Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 13.0069

Importance

Changes 0
Metric Value
dl 0
loc 46
ccs 28
cts 29
cp 0.9655
rs 6.6166
c 0
b 0
f 0
cc 13
nc 25
nop 1
crap 13.0069

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

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

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

Loading history...
516 1645
    }
517
518
    /**
519
     * Helper method to get reference id of ref* type references
520
     *
521
     * @internal
522
     *
523
     * @param mixed $reference
524
     *
525
     * @return mixed
526
     */
527 119
    public static function getReferenceId($reference, string $storeAs)
528
    {
529 119
        return $storeAs === self::REFERENCE_STORE_AS_ID ? $reference : $reference[self::getReferencePrefix($storeAs) . 'id'];
530
    }
531
532
    /**
533
     * Returns the reference prefix used for a reference
534
     */
535 188
    private static function getReferencePrefix(string $storeAs) : string
536
    {
537 188
        if (! in_array($storeAs, [self::REFERENCE_STORE_AS_REF, self::REFERENCE_STORE_AS_DB_REF, self::REFERENCE_STORE_AS_DB_REF_WITH_DB])) {
538
            throw new LogicException('Can only get a reference prefix for DBRef and reference arrays');
539
        }
540
541 188
        return $storeAs === self::REFERENCE_STORE_AS_REF ? '' : '$';
542
    }
543
544
    /**
545
     * Returns a fully qualified field name for a given reference
546
     *
547
     * @internal
548
     *
549
     * @param string $pathPrefix The field path prefix
550
     */
551 142
    public static function getReferenceFieldName(string $storeAs, string $pathPrefix = '') : string
552
    {
553 142
        if ($storeAs === self::REFERENCE_STORE_AS_ID) {
554 101
            return $pathPrefix;
555
        }
556
557 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...
558
    }
559
560
    /**
561
     * {@inheritDoc}
562
     */
563 1572
    public function getReflectionClass() : ReflectionClass
564
    {
565 1572
        return $this->reflClass;
566
    }
567
568
    /**
569
     * {@inheritDoc}
570
     */
571 349
    public function isIdentifier($fieldName) : bool
572
    {
573 349
        return $this->identifier === $fieldName;
574
    }
575
576
    /**
577
     * Sets the mapped identifier field of this class.
578
     *
579
     * @internal
580
     */
581 1019
    public function setIdentifier(?string $identifier) : void
582
    {
583 1019
        $this->identifier = $identifier;
584 1019
    }
585
586
    /**
587
     * {@inheritDoc}
588
     *
589
     * Since MongoDB only allows exactly one identifier field
590
     * this will always return an array with only one value
591
     */
592 12
    public function getIdentifier() : array
593
    {
594 12
        return [$this->identifier];
595
    }
596
597
    /**
598
     * Since MongoDB only allows exactly one identifier field
599
     * this will always return an array with only one value
600
     *
601
     * return (string|null)[]
602
     */
603 104
    public function getIdentifierFieldNames() : array
604
    {
605 104
        return [$this->identifier];
606
    }
607
608
    /**
609
     * {@inheritDoc}
610
     */
611 992
    public function hasField($fieldName) : bool
612
    {
613 992
        return isset($this->fieldMappings[$fieldName]);
614
    }
615
616
    /**
617
     * Sets the inheritance type used by the class and it's subclasses.
618
     */
619 1041
    public function setInheritanceType(int $type) : void
620
    {
621 1041
        $this->inheritanceType = $type;
622 1041
    }
623
624
    /**
625
     * Checks whether a mapped field is inherited from an entity superclass.
626
     */
627 1561
    public function isInheritedField(string $fieldName) : bool
628
    {
629 1561
        return isset($this->fieldMappings[$fieldName]['inherited']);
630
    }
631
632
    /**
633
     * Registers a custom repository class for the document class.
634
     */
635 963
    public function setCustomRepositoryClass(?string $repositoryClassName) : void
636
    {
637 963
        if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
638
            return;
639
        }
640
641 963
        $this->customRepositoryClassName = $repositoryClassName;
642 963
    }
643
644
    /**
645
     * Dispatches the lifecycle event of the given document by invoking all
646
     * registered callbacks.
647
     *
648
     * @throws InvalidArgumentException If document class is not this class or
649
     *                                   a Proxy of this class.
650
     */
651 665
    public function invokeLifecycleCallbacks(string $event, object $document, ?array $arguments = null) : void
652
    {
653 665
        if (! $document instanceof $this->name) {
654 1
            throw new InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"', $this->name, get_class($document)));
655
        }
656
657 664
        if (empty($this->lifecycleCallbacks[$event])) {
658 649
            return;
659
        }
660
661 191
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
662 191
            if ($arguments !== null) {
663 190
                $document->$callback(...$arguments);
664
            } else {
665 2
                $document->$callback();
666
            }
667
        }
668 191
    }
669
670
    /**
671
     * Checks whether the class has callbacks registered for a lifecycle event.
672
     */
673
    public function hasLifecycleCallbacks(string $event) : bool
674
    {
675
        return ! empty($this->lifecycleCallbacks[$event]);
676
    }
677
678
    /**
679
     * Gets the registered lifecycle callbacks for an event.
680
     */
681
    public function getLifecycleCallbacks(string $event) : array
682
    {
683
        return $this->lifecycleCallbacks[$event] ?? [];
684
    }
685
686
    /**
687
     * Adds a lifecycle callback for documents of this class.
688
     *
689
     * If the callback is already registered, this is a NOOP.
690
     */
691 925
    public function addLifecycleCallback(string $callback, string $event) : void
692
    {
693 925
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
694 1
            return;
695
        }
696
697 925
        $this->lifecycleCallbacks[$event][] = $callback;
698 925
    }
699
700
    /**
701
     * Sets the lifecycle callbacks for documents of this class.
702
     *
703
     * Any previously registered callbacks are overwritten.
704
     */
705 1018
    public function setLifecycleCallbacks(array $callbacks) : void
706
    {
707 1018
        $this->lifecycleCallbacks = $callbacks;
708 1018
    }
709
710
    /**
711
     * Registers a method for loading document data before field hydration.
712
     *
713
     * Note: A method may be registered multiple times for different fields.
714
     * it will be invoked only once for the first field found.
715
     *
716
     * @param array|string $fields Database field name(s)
717
     */
718 14
    public function registerAlsoLoadMethod(string $method, $fields) : void
719
    {
720 14
        $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : [$fields];
721 14
    }
722
723
    /**
724
     * Sets the AlsoLoad methods for documents of this class.
725
     *
726
     * Any previously registered methods are overwritten.
727
     */
728 1018
    public function setAlsoLoadMethods(array $methods) : void
729
    {
730 1018
        $this->alsoLoadMethods = $methods;
731 1018
    }
732
733
    /**
734
     * Sets the discriminator field.
735
     *
736
     * The field name is the the unmapped database field. Discriminator values
737
     * are only used to discern the hydration class and are not mapped to class
738
     * properties.
739
     *
740
     * @param array|string|null $discriminatorField
741
     *
742
     * @throws MappingException If the discriminator field conflicts with the
743
     *                          "name" attribute of a mapped field.
744
     */
745 1050
    public function setDiscriminatorField($discriminatorField) : void
746
    {
747 1050
        if ($this->isFile) {
748
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
749
        }
750
751 1050
        if ($discriminatorField === null) {
752 969
            $this->discriminatorField = null;
753
754 969
            return;
755
        }
756
757
        // @todo: deprecate, document and remove this:
758
        // Handle array argument with name/fieldName keys for BC
759 199
        if (is_array($discriminatorField)) {
760
            if (isset($discriminatorField['name'])) {
761
                $discriminatorField = $discriminatorField['name'];
762
            } elseif (isset($discriminatorField['fieldName'])) {
763
                $discriminatorField = $discriminatorField['fieldName'];
764
            }
765
        }
766
767 199
        foreach ($this->fieldMappings as $fieldMapping) {
768 4
            if ($discriminatorField === $fieldMapping['name']) {
769 1
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
770
            }
771
        }
772
773 198
        $this->discriminatorField = $discriminatorField;
774 198
    }
775
776
    /**
777
     * Sets the discriminator values used by this class.
778
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
779
     *
780
     * @throws MappingException
781
     */
782 1039
    public function setDiscriminatorMap(array $map) : void
783
    {
784 1039
        if ($this->isFile) {
785
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
786
        }
787
788 1039
        $this->subClasses         = [];
789 1039
        $this->discriminatorMap   = [];
790 1039
        $this->discriminatorValue = null;
791
792 1039
        foreach ($map as $value => $className) {
793 186
            $this->discriminatorMap[$value] = $className;
794 186
            if ($this->name === $className) {
795 177
                $this->discriminatorValue = $value;
796
            } else {
797 184
                if (! class_exists($className)) {
798
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
799
                }
800 184
                if (is_subclass_of($className, $this->name)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $this->name can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
801 169
                    $this->subClasses[] = $className;
802
                }
803
            }
804
        }
805 1039
    }
806
807
    /**
808
     * Sets the default discriminator value to be used for this class
809
     * Used for SINGLE_TABLE inheritance mapping strategies if the document has no discriminator value
810
     *
811
     * @throws MappingException
812
     */
813 1021
    public function setDefaultDiscriminatorValue(?string $defaultDiscriminatorValue) : void
814
    {
815 1021
        if ($this->isFile) {
816
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
817
        }
818
819 1021
        if ($defaultDiscriminatorValue === null) {
820 1018
            $this->defaultDiscriminatorValue = null;
821
822 1018
            return;
823
        }
824
825 117
        if (! array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
826
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
827
        }
828
829 117
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
830 117
    }
831
832
    /**
833
     * Sets the discriminator value for this class.
834
     * Used for JOINED/SINGLE_TABLE inheritance and multiple document types in a single
835
     * collection.
836
     *
837
     * @throws MappingException
838
     */
839 4
    public function setDiscriminatorValue(string $value) : void
840
    {
841 4
        if ($this->isFile) {
842
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
843
        }
844
845 4
        $this->discriminatorMap[$value] = $this->name;
846 4
        $this->discriminatorValue       = $value;
847 4
    }
848
849
    /**
850
     * Add a index for this Document.
851
     */
852 269
    public function addIndex(array $keys, array $options = []) : void
853
    {
854 269
        $this->indexes[] = [
855
            'keys' => array_map(static function ($value) {
856 269
                if ($value === 1 || $value === -1) {
857 114
                    return $value;
858
                }
859 269
                if (is_string($value)) {
860 269
                    $lower = strtolower($value);
861 269
                    if ($lower === 'asc') {
862 262
                        return 1;
863
                    }
864
865 121
                    if ($lower === 'desc') {
866
                        return -1;
867
                    }
868
                }
869
870 121
                return $value;
871 269
            }, $keys),
872 269
            'options' => $options,
873
        ];
874 269
    }
875
876
    /**
877
     * Returns the array of indexes for this Document.
878
     */
879 50
    public function getIndexes() : array
880
    {
881 50
        return $this->indexes;
882
    }
883
884
    /**
885
     * Checks whether this document has indexes or not.
886
     */
887
    public function hasIndexes() : bool
888
    {
889
        return $this->indexes !== [];
890
    }
891
892
    /**
893
     * Set shard key for this Document.
894
     *
895
     * @throws MappingException
896
     */
897 150
    public function setShardKey(array $keys, array $options = []) : void
898
    {
899 150
        if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && $this->shardKey !== []) {
900 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...
901
        }
902
903 150
        if ($this->isEmbeddedDocument) {
904 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...
905
        }
906
907 148
        foreach (array_keys($keys) as $field) {
908 148
            if (! isset($this->fieldMappings[$field])) {
909 141
                continue;
910
            }
911
912 120
            if (in_array($this->fieldMappings[$field]['type'], ['many', 'collection'])) {
913 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...
914
            }
915
916 117
            if ($this->fieldMappings[$field]['strategy'] !== self::STORAGE_STRATEGY_SET) {
917 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...
918
            }
919
        }
920
921 144
        $this->shardKey = [
922
            'keys' => array_map(static function ($value) {
923 144
                if ($value === 1 || $value === -1) {
924 5
                    return $value;
925
                }
926 144
                if (is_string($value)) {
927 144
                    $lower = strtolower($value);
928 144
                    if ($lower === 'asc') {
929 142
                        return 1;
930
                    }
931
932 116
                    if ($lower === 'desc') {
933
                        return -1;
934
                    }
935
                }
936
937 116
                return $value;
938 144
            }, $keys),
939 144
            'options' => $options,
940
        ];
941 144
    }
942
943 27
    public function getShardKey() : array
944
    {
945 27
        return $this->shardKey;
946
    }
947
948
    /**
949
     * Checks whether this document has shard key or not.
950
     */
951 1272
    public function isSharded() : bool
952
    {
953 1272
        return $this->shardKey !== [];
954
    }
955
956
    /**
957
     * Sets the read preference used by this class.
958
     */
959 1018
    public function setReadPreference(?string $readPreference, array $tags) : void
960
    {
961 1018
        $this->readPreference     = $readPreference;
962 1018
        $this->readPreferenceTags = $tags;
963 1018
    }
964
965
    /**
966
     * Sets the write concern used by this class.
967
     *
968
     * @param string|int|null $writeConcern
969
     */
970 1028
    public function setWriteConcern($writeConcern) : void
971
    {
972 1028
        $this->writeConcern = $writeConcern;
973 1028
    }
974
975
    /**
976
     * @return int|string|null
977
     */
978 11
    public function getWriteConcern()
979
    {
980 11
        return $this->writeConcern;
981
    }
982
983
    /**
984
     * Whether there is a write concern configured for this class.
985
     */
986 613
    public function hasWriteConcern() : bool
987
    {
988 613
        return $this->writeConcern !== null;
989
    }
990
991
    /**
992
     * Sets the change tracking policy used by this class.
993
     */
994 1019
    public function setChangeTrackingPolicy(int $policy) : void
995
    {
996 1019
        $this->changeTrackingPolicy = $policy;
997 1019
    }
998
999
    /**
1000
     * Whether the change tracking policy of this class is "deferred explicit".
1001
     */
1002 69
    public function isChangeTrackingDeferredExplicit() : bool
1003
    {
1004 69
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT;
1005
    }
1006
1007
    /**
1008
     * Whether the change tracking policy of this class is "deferred implicit".
1009
     */
1010 624
    public function isChangeTrackingDeferredImplicit() : bool
1011
    {
1012 624
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT;
1013
    }
1014
1015
    /**
1016
     * Whether the change tracking policy of this class is "notify".
1017
     */
1018 348
    public function isChangeTrackingNotify() : bool
1019
    {
1020 348
        return $this->changeTrackingPolicy === self::CHANGETRACKING_NOTIFY;
1021
    }
1022
1023
    /**
1024
     * Gets the ReflectionProperties of the mapped class.
1025
     */
1026 1
    public function getReflectionProperties() : array
1027
    {
1028 1
        return $this->reflFields;
1029
    }
1030
1031
    /**
1032
     * Gets a ReflectionProperty for a specific field of the mapped class.
1033
     */
1034 103
    public function getReflectionProperty(string $name) : ReflectionProperty
1035
    {
1036 103
        return $this->reflFields[$name];
1037
    }
1038
1039
    /**
1040
     * {@inheritDoc}
1041
     */
1042 1571
    public function getName() : string
1043
    {
1044 1571
        return $this->name;
1045
    }
1046
1047
    /**
1048
     * Returns the database this Document is mapped to.
1049
     */
1050 1473
    public function getDatabase() : ?string
1051
    {
1052 1473
        return $this->db;
1053
    }
1054
1055
    /**
1056
     * Set the database this Document is mapped to.
1057
     */
1058 167
    public function setDatabase(?string $db) : void
1059
    {
1060 167
        $this->db = $db;
1061 167
    }
1062
1063
    /**
1064
     * Get the collection this Document is mapped to.
1065
     */
1066 1470
    public function getCollection() : string
1067
    {
1068 1470
        return $this->collection;
1069
    }
1070
1071
    /**
1072
     * Sets the collection this Document is mapped to.
1073
     *
1074
     * @param array|string $name
1075
     *
1076
     * @throws InvalidArgumentException
1077
     */
1078 1645
    public function setCollection($name) : void
1079
    {
1080 1645
        if (is_array($name)) {
1081 1
            if (! isset($name['name'])) {
1082
                throw new InvalidArgumentException('A name key is required when passing an array to setCollection()');
1083
            }
1084 1
            $this->collectionCapped = $name['capped'] ?? false;
1085 1
            $this->collectionSize   = $name['size'] ?? 0;
1086 1
            $this->collectionMax    = $name['max'] ?? 0;
1087 1
            $this->collection       = $name['name'];
1088
        } else {
1089 1645
            $this->collection = $name;
1090
        }
1091 1645
    }
1092
1093 125
    public function getBucketName() : ?string
1094
    {
1095 125
        return $this->bucketName;
1096
    }
1097
1098 1
    public function setBucketName(string $bucketName) : void
1099
    {
1100 1
        $this->bucketName = $bucketName;
1101 1
        $this->setCollection($bucketName . '.files');
1102 1
    }
1103
1104 12
    public function getChunkSizeBytes() : ?int
1105
    {
1106 12
        return $this->chunkSizeBytes;
1107
    }
1108
1109 128
    public function setChunkSizeBytes(int $chunkSizeBytes) : void
1110
    {
1111 128
        $this->chunkSizeBytes = $chunkSizeBytes;
1112 128
    }
1113
1114
    /**
1115
     * Get whether or not the documents collection is capped.
1116
     */
1117 11
    public function getCollectionCapped() : bool
1118
    {
1119 11
        return $this->collectionCapped;
1120
    }
1121
1122
    /**
1123
     * Set whether or not the documents collection is capped.
1124
     */
1125 1
    public function setCollectionCapped(bool $bool) : void
1126
    {
1127 1
        $this->collectionCapped = $bool;
1128 1
    }
1129
1130
    /**
1131
     * Get the collection size
1132
     */
1133 11
    public function getCollectionSize() : ?int
1134
    {
1135 11
        return $this->collectionSize;
1136
    }
1137
1138
    /**
1139
     * Set the collection size.
1140
     */
1141 1
    public function setCollectionSize(int $size) : void
1142
    {
1143 1
        $this->collectionSize = $size;
1144 1
    }
1145
1146
    /**
1147
     * Get the collection max.
1148
     */
1149 11
    public function getCollectionMax() : ?int
1150
    {
1151 11
        return $this->collectionMax;
1152
    }
1153
1154
    /**
1155
     * Set the collection max.
1156
     */
1157 1
    public function setCollectionMax(int $max) : void
1158
    {
1159 1
        $this->collectionMax = $max;
1160 1
    }
1161
1162
    /**
1163
     * Returns TRUE if this Document is mapped to a collection FALSE otherwise.
1164
     */
1165
    public function isMappedToCollection() : bool
1166
    {
1167
        return $this->collection !== '' && $this->collection !== null;
1168
    }
1169
1170
    /**
1171
     * Validates the storage strategy of a mapping for consistency
1172
     *
1173
     * @throws MappingException
1174
     */
1175 1589
    private function applyStorageStrategy(array &$mapping) : void
1176
    {
1177 1589
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1178 1566
            return;
1179
        }
1180
1181
        switch (true) {
1182 1551
            case $mapping['type'] === 'many':
1183 1236
                $defaultStrategy   = CollectionHelper::DEFAULT_STRATEGY;
1184
                $allowedStrategies = [
1185 1236
                    self::STORAGE_STRATEGY_PUSH_ALL,
1186 1236
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1187 1236
                    self::STORAGE_STRATEGY_SET,
1188 1236
                    self::STORAGE_STRATEGY_SET_ARRAY,
1189 1236
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1190 1236
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1191
                ];
1192 1236
                break;
1193
1194 1539
            case $mapping['type'] === 'one':
1195 1245
                $defaultStrategy   = self::STORAGE_STRATEGY_SET;
1196 1245
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1197 1245
                break;
1198
1199
            default:
1200 1498
                $defaultStrategy   = self::STORAGE_STRATEGY_SET;
1201 1498
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1202 1498
                $type              = Type::getType($mapping['type']);
1203 1498
                if ($type instanceof Incrementable) {
1204 981
                    $allowedStrategies[] = self::STORAGE_STRATEGY_INCREMENT;
1205
                }
1206
        }
1207
1208 1551
        if (! isset($mapping['strategy'])) {
1209 1543
            $mapping['strategy'] = $defaultStrategy;
1210
        }
1211
1212 1551
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1213
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1214
        }
1215
1216 1551
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1217 1551
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1218 1
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1219
        }
1220 1550
    }
1221
1222
    /**
1223
     * Map a single embedded document.
1224
     */
1225 6
    public function mapOneEmbedded(array $mapping) : void
1226
    {
1227 6
        $mapping['embedded'] = true;
1228 6
        $mapping['type']     = 'one';
1229 6
        $this->mapField($mapping);
1230 5
    }
1231
1232
    /**
1233
     * Map a collection of embedded documents.
1234
     */
1235 6
    public function mapManyEmbedded(array $mapping) : void
1236
    {
1237 6
        $mapping['embedded'] = true;
1238 6
        $mapping['type']     = 'many';
1239 6
        $this->mapField($mapping);
1240 6
    }
1241
1242
    /**
1243
     * Map a single document reference.
1244
     */
1245 3
    public function mapOneReference(array $mapping) : void
1246
    {
1247 3
        $mapping['reference'] = true;
1248 3
        $mapping['type']      = 'one';
1249 3
        $this->mapField($mapping);
1250 3
    }
1251
1252
    /**
1253
     * Map a collection of document references.
1254
     */
1255 1
    public function mapManyReference(array $mapping) : void
1256
    {
1257 1
        $mapping['reference'] = true;
1258 1
        $mapping['type']      = 'many';
1259 1
        $this->mapField($mapping);
1260 1
    }
1261
1262
    /**
1263
     * Adds a field mapping without completing/validating it.
1264
     * This is mainly used to add inherited field mappings to derived classes.
1265
     *
1266
     * @internal
1267
     */
1268 191
    public function addInheritedFieldMapping(array $fieldMapping) : void
1269
    {
1270 191
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1271
1272 191
        if (! isset($fieldMapping['association'])) {
1273 191
            return;
1274
        }
1275
1276 141
        $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1277 141
    }
1278
1279
    /**
1280
     * Adds an association mapping without completing/validating it.
1281
     * This is mainly used to add inherited association mappings to derived classes.
1282
     *
1283
     * @internal
1284
     *
1285
     * @throws MappingException
1286
     */
1287 142
    public function addInheritedAssociationMapping(array $mapping) : void
1288
    {
1289 142
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1290 142
    }
1291
1292
    /**
1293
     * Checks whether the class has a mapped association with the given field name.
1294
     */
1295 31
    public function hasReference(string $fieldName) : bool
1296
    {
1297 31
        return isset($this->fieldMappings[$fieldName]['reference']);
1298
    }
1299
1300
    /**
1301
     * Checks whether the class has a mapped embed with the given field name.
1302
     */
1303 4
    public function hasEmbed(string $fieldName) : bool
1304
    {
1305 4
        return isset($this->fieldMappings[$fieldName]['embedded']);
1306
    }
1307
1308
    /**
1309
     * {@inheritDoc}
1310
     *
1311
     * Checks whether the class has a mapped association (embed or reference) with the given field name.
1312
     */
1313 6
    public function hasAssociation($fieldName) : bool
1314
    {
1315 6
        return $this->hasReference($fieldName) || $this->hasEmbed($fieldName);
1316
    }
1317
1318
    /**
1319
     * {@inheritDoc}
1320
     *
1321
     * Checks whether the class has a mapped reference or embed for the specified field and
1322
     * is a single valued association.
1323
     */
1324
    public function isSingleValuedAssociation($fieldName) : bool
1325
    {
1326
        return $this->isSingleValuedReference($fieldName) || $this->isSingleValuedEmbed($fieldName);
1327
    }
1328
1329
    /**
1330
     * {@inheritDoc}
1331
     *
1332
     * Checks whether the class has a mapped reference or embed for the specified field and
1333
     * is a collection valued association.
1334
     */
1335
    public function isCollectionValuedAssociation($fieldName) : bool
1336
    {
1337
        return $this->isCollectionValuedReference($fieldName) || $this->isCollectionValuedEmbed($fieldName);
1338
    }
1339
1340
    /**
1341
     * Checks whether the class has a mapped association for the specified field
1342
     * and if yes, checks whether it is a single-valued association (to-one).
1343
     */
1344 1
    public function isSingleValuedReference(string $fieldName) : bool
1345
    {
1346 1
        return isset($this->fieldMappings[$fieldName]['association']) &&
1347 1
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_ONE;
1348
    }
1349
1350
    /**
1351
     * Checks whether the class has a mapped association for the specified field
1352
     * and if yes, checks whether it is a collection-valued association (to-many).
1353
     */
1354
    public function isCollectionValuedReference(string $fieldName) : bool
1355
    {
1356
        return isset($this->fieldMappings[$fieldName]['association']) &&
1357
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_MANY;
1358
    }
1359
1360
    /**
1361
     * Checks whether the class has a mapped embedded document for the specified field
1362
     * and if yes, checks whether it is a single-valued association (to-one).
1363
     */
1364
    public function isSingleValuedEmbed(string $fieldName) : bool
1365
    {
1366
        return isset($this->fieldMappings[$fieldName]['association']) &&
1367
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_ONE;
1368
    }
1369
1370
    /**
1371
     * Checks whether the class has a mapped embedded document for the specified field
1372
     * and if yes, checks whether it is a collection-valued association (to-many).
1373
     */
1374
    public function isCollectionValuedEmbed(string $fieldName) : bool
1375
    {
1376
        return isset($this->fieldMappings[$fieldName]['association']) &&
1377
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_MANY;
1378
    }
1379
1380
    /**
1381
     * Sets the ID generator used to generate IDs for instances of this class.
1382
     */
1383 1507
    public function setIdGenerator(AbstractIdGenerator $generator) : void
1384
    {
1385 1507
        $this->idGenerator = $generator;
1386 1507
    }
1387
1388
    /**
1389
     * Casts the identifier to its portable PHP type.
1390
     *
1391
     * @param mixed $id
1392
     *
1393
     * @return mixed $id
1394
     */
1395 661
    public function getPHPIdentifierValue($id)
1396
    {
1397 661
        $idType = $this->fieldMappings[$this->identifier]['type'];
1398
1399 661
        return Type::getType($idType)->convertToPHPValue($id);
1400
    }
1401
1402
    /**
1403
     * Casts the identifier to its database type.
1404
     *
1405
     * @param mixed $id
1406
     *
1407
     * @return mixed $id
1408
     */
1409 729
    public function getDatabaseIdentifierValue($id)
1410
    {
1411 729
        $idType = $this->fieldMappings[$this->identifier]['type'];
1412
1413 729
        return Type::getType($idType)->convertToDatabaseValue($id);
1414
    }
1415
1416
    /**
1417
     * Sets the document identifier of a document.
1418
     *
1419
     * The value will be converted to a PHP type before being set.
1420
     *
1421
     * @param mixed $id
1422
     */
1423 591
    public function setIdentifierValue(object $document, $id) : void
1424
    {
1425 591
        $id = $this->getPHPIdentifierValue($id);
1426 591
        $this->reflFields[$this->identifier]->setValue($document, $id);
1427 591
    }
1428
1429
    /**
1430
     * Gets the document identifier as a PHP type.
1431
     *
1432
     * @return mixed $id
1433
     */
1434 671
    public function getIdentifierValue(object $document)
1435
    {
1436 671
        return $this->reflFields[$this->identifier]->getValue($document);
1437
    }
1438
1439
    /**
1440
     * {@inheritDoc}
1441
     *
1442
     * Since MongoDB only allows exactly one identifier field this is a proxy
1443
     * to {@see getIdentifierValue()} and returns an array with the identifier
1444
     * field as a key.
1445
     */
1446
    public function getIdentifierValues($object) : array
1447
    {
1448
        return [$this->identifier => $this->getIdentifierValue($object)];
1449
    }
1450
1451
    /**
1452
     * Get the document identifier object as a database type.
1453
     *
1454
     * @return mixed $id
1455
     */
1456 30
    public function getIdentifierObject(object $document)
1457
    {
1458 30
        return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));
1459
    }
1460
1461
    /**
1462
     * Sets the specified field to the specified value on the given document.
1463
     *
1464
     * @param mixed $value
1465
     */
1466 8
    public function setFieldValue(object $document, string $field, $value) : void
1467
    {
1468 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...
1469
            //property changes to an uninitialized proxy will not be tracked or persisted,
1470
            //so the proxy needs to be loaded first.
1471 1
            $document->initializeProxy();
1472
        }
1473
1474 8
        $this->reflFields[$field]->setValue($document, $value);
1475 8
    }
1476
1477
    /**
1478
     * Gets the specified field's value off the given document.
1479
     *
1480
     * @return mixed
1481
     */
1482 33
    public function getFieldValue(object $document, string $field)
1483
    {
1484 33
        if ($document instanceof GhostObjectInterface && $field !== $this->identifier && ! $document->isProxyInitialized()) {
0 ignored issues
show
Bug introduced by
The class ProxyManager\Proxy\GhostObjectInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

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

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

2. Missing use statement

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

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

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

Loading history...
1485 1
            $document->initializeProxy();
1486
        }
1487
1488 33
        return $this->reflFields[$field]->getValue($document);
1489
    }
1490
1491
    /**
1492
     * Gets the mapping of a field.
1493
     *
1494
     * @throws MappingException If the $fieldName is not found in the fieldMappings array.
1495
     */
1496 199
    public function getFieldMapping(string $fieldName) : array
1497
    {
1498 199
        if (! isset($this->fieldMappings[$fieldName])) {
1499 6
            throw MappingException::mappingNotFound($this->name, $fieldName);
1500
        }
1501
1502 197
        return $this->fieldMappings[$fieldName];
1503
    }
1504
1505
    /**
1506
     * Gets mappings of fields holding embedded document(s).
1507
     */
1508 602
    public function getEmbeddedFieldsMappings() : array
1509
    {
1510 602
        return array_filter(
1511 602
            $this->associationMappings,
1512
            static function ($assoc) {
1513 462
                return ! empty($assoc['embedded']);
1514 602
            }
1515
        );
1516
    }
1517
1518
    /**
1519
     * Gets the field mapping by its DB name.
1520
     * E.g. it returns identifier's mapping when called with _id.
1521
     *
1522
     * @throws MappingException
1523
     */
1524 17
    public function getFieldMappingByDbFieldName(string $dbFieldName) : array
1525
    {
1526 17
        foreach ($this->fieldMappings as $mapping) {
1527 17
            if ($mapping['name'] === $dbFieldName) {
1528 15
                return $mapping;
1529
            }
1530
        }
1531
1532 2
        throw MappingException::mappingNotFoundByDbName($this->name, $dbFieldName);
1533
    }
1534
1535
    /**
1536
     * Check if the field is not null.
1537
     */
1538 1
    public function isNullable(string $fieldName) : bool
1539
    {
1540 1
        $mapping = $this->getFieldMapping($fieldName);
1541
1542 1
        return isset($mapping['nullable']) && $mapping['nullable'] === true;
1543
    }
1544
1545
    /**
1546
     * Checks whether the document has a discriminator field and value configured.
1547
     */
1548
    public function hasDiscriminator() : bool
1549
    {
1550
        return isset($this->discriminatorField, $this->discriminatorValue);
1551
    }
1552
1553
    /**
1554
     * Sets the type of Id generator to use for the mapped class.
1555
     */
1556 1018
    public function setIdGeneratorType(int $generatorType) : void
1557
    {
1558 1018
        $this->generatorType = $generatorType;
1559 1018
    }
1560
1561
    /**
1562
     * Sets the Id generator options.
1563
     */
1564
    public function setIdGeneratorOptions(array $generatorOptions) : void
1565
    {
1566
        $this->generatorOptions = $generatorOptions;
1567
    }
1568
1569 629
    public function isInheritanceTypeNone() : bool
1570
    {
1571 629
        return $this->inheritanceType === self::INHERITANCE_TYPE_NONE;
1572
    }
1573
1574
    /**
1575
     * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.
1576
     */
1577 1017
    public function isInheritanceTypeSingleCollection() : bool
1578
    {
1579 1017
        return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION;
1580
    }
1581
1582
    /**
1583
     * Checks whether the mapped class uses the COLLECTION_PER_CLASS inheritance mapping strategy.
1584
     */
1585
    public function isInheritanceTypeCollectionPerClass() : bool
1586
    {
1587
        return $this->inheritanceType === self::INHERITANCE_TYPE_COLLECTION_PER_CLASS;
1588
    }
1589
1590
    /**
1591
     * Sets the mapped subclasses of this class.
1592
     *
1593
     * @param string[] $subclasses The names of all mapped subclasses.
1594
     */
1595 2
    public function setSubclasses(array $subclasses) : void
1596
    {
1597 2
        foreach ($subclasses as $subclass) {
1598 2
            $this->subClasses[] = $subclass;
1599
        }
1600 2
    }
1601
1602
    /**
1603
     * Sets the parent class names.
1604
     * Assumes that the class names in the passed array are in the order:
1605
     * directParent -> directParentParent -> directParentParentParent ... -> root.
1606
     *
1607
     * @param string[] $classNames
1608
     */
1609 1560
    public function setParentClasses(array $classNames) : void
1610
    {
1611 1560
        $this->parentClasses = $classNames;
1612
1613 1560
        if (count($classNames) <= 0) {
1614 1559
            return;
1615
        }
1616
1617 175
        $this->rootDocumentName = (string) array_pop($classNames);
1618 175
    }
1619
1620
    /**
1621
     * Checks whether the class will generate a new \MongoDB\BSON\ObjectId instance for us.
1622
     */
1623
    public function isIdGeneratorAuto() : bool
1624
    {
1625
        return $this->generatorType === self::GENERATOR_TYPE_AUTO;
1626
    }
1627
1628
    /**
1629
     * Checks whether the class will use a collection to generate incremented identifiers.
1630
     */
1631
    public function isIdGeneratorIncrement() : bool
1632
    {
1633
        return $this->generatorType === self::GENERATOR_TYPE_INCREMENT;
1634
    }
1635
1636
    /**
1637
     * Checks whether the class will generate a uuid id.
1638
     */
1639
    public function isIdGeneratorUuid() : bool
1640
    {
1641
        return $this->generatorType === self::GENERATOR_TYPE_UUID;
1642
    }
1643
1644
    /**
1645
     * Checks whether the class uses no id generator.
1646
     */
1647
    public function isIdGeneratorNone() : bool
1648
    {
1649
        return $this->generatorType === self::GENERATOR_TYPE_NONE;
1650
    }
1651
1652
    /**
1653
     * Sets the version field mapping used for versioning. Sets the default
1654
     * value to use depending on the column type.
1655
     *
1656
     * @throws LockException
1657
     */
1658 163
    public function setVersionMapping(array &$mapping) : void
1659
    {
1660 163
        if (! in_array($mapping['type'], [Type::INT, Type::INTEGER, Type::DATE, Type::DATE_IMMUTABLE, Type::DECIMAL128], true)) {
1661 1
            throw LockException::invalidVersionFieldType($mapping['type']);
1662
        }
1663
1664 162
        $this->isVersioned  = true;
1665 162
        $this->versionField = $mapping['fieldName'];
1666 162
    }
1667
1668
    /**
1669
     * Sets whether this class is to be versioned for optimistic locking.
1670
     */
1671 1018
    public function setVersioned(bool $bool) : void
1672
    {
1673 1018
        $this->isVersioned = $bool;
1674 1018
    }
1675
1676
    /**
1677
     * Sets the name of the field that is to be used for versioning if this class is
1678
     * versioned for optimistic locking.
1679
     */
1680 1018
    public function setVersionField(?string $versionField) : void
1681
    {
1682 1018
        $this->versionField = $versionField;
1683 1018
    }
1684
1685
    /**
1686
     * Sets the version field mapping used for versioning. Sets the default
1687
     * value to use depending on the column type.
1688
     *
1689
     * @throws LockException
1690
     */
1691 28
    public function setLockMapping(array &$mapping) : void
1692
    {
1693 28
        if ($mapping['type'] !== 'int') {
1694 1
            throw LockException::invalidLockFieldType($mapping['type']);
1695
        }
1696
1697 27
        $this->isLockable = true;
1698 27
        $this->lockField  = $mapping['fieldName'];
1699 27
    }
1700
1701
    /**
1702
     * Sets whether this class is to allow pessimistic locking.
1703
     */
1704
    public function setLockable(bool $bool) : void
1705
    {
1706
        $this->isLockable = $bool;
1707
    }
1708
1709
    /**
1710
     * Sets the name of the field that is to be used for storing whether a document
1711
     * is currently locked or not.
1712
     */
1713
    public function setLockField(string $lockField) : void
1714
    {
1715
        $this->lockField = $lockField;
1716
    }
1717
1718
    /**
1719
     * Marks this class as read only, no change tracking is applied to it.
1720
     */
1721 5
    public function markReadOnly() : void
1722
    {
1723 5
        $this->isReadOnly = true;
1724 5
    }
1725
1726
    /**
1727
     * {@inheritDoc}
1728
     */
1729
    public function getFieldNames() : array
1730
    {
1731
        return array_keys($this->fieldMappings);
1732
    }
1733
1734
    /**
1735
     * {@inheritDoc}
1736
     */
1737
    public function getAssociationNames() : array
1738
    {
1739
        return array_keys($this->associationMappings);
1740
    }
1741
1742
    /**
1743
     * {@inheritDoc}
1744
     */
1745
    public function getTypeOfField($fieldName) : ?string
1746
    {
1747
        return isset($this->fieldMappings[$fieldName]) ?
1748
            $this->fieldMappings[$fieldName]['type'] : null;
1749
    }
1750
1751
    /**
1752
     * {@inheritDoc}
1753
     */
1754 5
    public function getAssociationTargetClass($assocName) : ?string
1755
    {
1756 5
        if (! isset($this->associationMappings[$assocName])) {
1757 2
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1758
        }
1759
1760 3
        return $this->associationMappings[$assocName]['targetDocument'];
1761
    }
1762
1763
    /**
1764
     * Retrieve the collectionClass associated with an association
1765
     */
1766
    public function getAssociationCollectionClass(string $assocName) : string
1767
    {
1768
        if (! isset($this->associationMappings[$assocName])) {
1769
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1770
        }
1771
1772
        if (! array_key_exists('collectionClass', $this->associationMappings[$assocName])) {
1773
            throw new InvalidArgumentException("collectionClass can only be applied to 'embedMany' and 'referenceMany' associations.");
1774
        }
1775
1776
        return $this->associationMappings[$assocName]['collectionClass'];
1777
    }
1778
1779
    /**
1780
     * {@inheritDoc}
1781
     */
1782
    public function isAssociationInverseSide($fieldName) : bool
1783
    {
1784
        throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1785
    }
1786
1787
    /**
1788
     * {@inheritDoc}
1789
     */
1790
    public function getAssociationMappedByTargetField($fieldName)
1791
    {
1792
        throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1793
    }
1794
1795
    /**
1796
     * Map a field.
1797
     *
1798
     * @throws MappingException
1799
     */
1800 1605
    public function mapField(array $mapping) : array
1801
    {
1802 1605
        if (! isset($mapping['fieldName']) && isset($mapping['name'])) {
1803 6
            $mapping['fieldName'] = $mapping['name'];
1804
        }
1805 1605
        if (! isset($mapping['fieldName']) || ! is_string($mapping['fieldName'])) {
1806
            throw MappingException::missingFieldName($this->name);
1807
        }
1808 1605
        if (! isset($mapping['name'])) {
1809 1604
            $mapping['name'] = $mapping['fieldName'];
1810
        }
1811
1812 1605
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1813 1
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, (string) $mapping['name']);
1814
        }
1815 1604
        if ($this->discriminatorField !== null && $this->discriminatorField === $mapping['name']) {
1816 1
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1817
        }
1818 1603
        if (isset($mapping['collectionClass'])) {
1819 122
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1820
        }
1821 1603
        if (! empty($mapping['collectionClass'])) {
1822 122
            $rColl = new ReflectionClass($mapping['collectionClass']);
1823 122
            if (! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1824 1
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1825
            }
1826
        }
1827
1828 1602
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1829 1
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1830
        }
1831
1832 1601
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : [];
1833
1834 1601
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
1835 1289
            $cascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1836
        }
1837
1838 1601
        if (isset($mapping['embedded'])) {
1839 1242
            unset($mapping['cascade']);
1840 1595
        } elseif (isset($mapping['cascade'])) {
1841 1037
            $mapping['cascade'] = $cascades;
1842
        }
1843
1844 1601
        $mapping['isCascadeRemove']  = in_array('remove', $cascades);
1845 1601
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1846 1601
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1847 1601
        $mapping['isCascadeMerge']   = in_array('merge', $cascades);
1848 1601
        $mapping['isCascadeDetach']  = in_array('detach', $cascades);
1849
1850 1601
        if (isset($mapping['id']) && $mapping['id'] === true) {
1851 1566
            $mapping['name']  = '_id';
1852 1566
            $this->identifier = $mapping['fieldName'];
1853 1566
            if (isset($mapping['strategy'])) {
1854 1560
                $this->generatorType = constant(self::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
1855
            }
1856 1566
            $this->generatorOptions = $mapping['options'] ?? [];
1857 1566
            switch ($this->generatorType) {
1858 1566
                case self::GENERATOR_TYPE_AUTO:
1859 1496
                    $mapping['type'] = 'id';
1860 1496
                    break;
1861
                default:
1862 212
                    if (! empty($this->generatorOptions['type'])) {
1863 56
                        $mapping['type'] = $this->generatorOptions['type'];
1864 156
                    } elseif (empty($mapping['type'])) {
1865 146
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
1866
                    }
1867
            }
1868 1566
            unset($this->generatorOptions['type']);
1869
        }
1870
1871 1601
        if (! isset($mapping['nullable'])) {
1872 41
            $mapping['nullable'] = false;
1873
        }
1874
1875 1601
        if (isset($mapping['reference'])
1876 1601
            && isset($mapping['storeAs'])
1877 1601
            && $mapping['storeAs'] === self::REFERENCE_STORE_AS_ID
1878 1601
            && ! isset($mapping['targetDocument'])
1879
        ) {
1880 3
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
1881
        }
1882
1883 1598
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
1884 1598
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
1885 4
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
1886
        }
1887
1888 1594
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
1889 1
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
1890
        }
1891
1892 1593
        if (isset($mapping['repositoryMethod']) && ! (empty($mapping['skip']) && empty($mapping['limit']) && empty($mapping['sort']))) {
1893 3
            throw MappingException::repositoryMethodCanNotBeCombinedWithSkipLimitAndSort($this->name, $mapping['fieldName']);
1894
        }
1895
1896 1590
        if (isset($mapping['reference']) && $mapping['type'] === 'one') {
1897 1153
            $mapping['association'] = self::REFERENCE_ONE;
1898
        }
1899 1590
        if (isset($mapping['reference']) && $mapping['type'] === 'many') {
1900 1093
            $mapping['association'] = self::REFERENCE_MANY;
1901
        }
1902 1590
        if (isset($mapping['embedded']) && $mapping['type'] === 'one') {
1903 1090
            $mapping['association'] = self::EMBED_ONE;
1904
        }
1905 1590
        if (isset($mapping['embedded']) && $mapping['type'] === 'many') {
1906 1136
            $mapping['association'] = self::EMBED_MANY;
1907
        }
1908
1909 1590
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
1910 955
            $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
1911
        }
1912
1913 1590
        if (isset($mapping['version'])) {
1914 163
            $mapping['notSaved'] = true;
1915 163
            $this->setVersionMapping($mapping);
1916
        }
1917 1590
        if (isset($mapping['lock'])) {
1918 28
            $mapping['notSaved'] = true;
1919 28
            $this->setLockMapping($mapping);
1920
        }
1921 1590
        $mapping['isOwningSide']  = true;
1922 1590
        $mapping['isInverseSide'] = false;
1923 1590
        if (isset($mapping['reference'])) {
1924 1221
            if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
1925 153
                $mapping['isOwningSide']  = true;
1926 153
                $mapping['isInverseSide'] = false;
1927
            }
1928 1221
            if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
1929 932
                $mapping['isInverseSide'] = true;
1930 932
                $mapping['isOwningSide']  = false;
1931
            }
1932 1221
            if (isset($mapping['repositoryMethod'])) {
1933 127
                $mapping['isInverseSide'] = true;
1934 127
                $mapping['isOwningSide']  = false;
1935
            }
1936 1221
            if (! isset($mapping['orphanRemoval'])) {
1937 1202
                $mapping['orphanRemoval'] = false;
1938
            }
1939
        }
1940
1941 1590
        if (! empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || ! $mapping['isInverseSide'])) {
1942
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
1943
        }
1944
1945 1590
        if ($this->isFile && ! $this->isAllowedGridFSField($mapping['name'])) {
1946 1
            throw MappingException::fieldNotAllowedForGridFS($this->name, $mapping['fieldName']);
1947
        }
1948
1949 1589
        $this->applyStorageStrategy($mapping);
1950 1588
        $this->checkDuplicateMapping($mapping);
1951 1588
        $this->typeRequirementsAreMet($mapping['type']);
1952
1953 1588
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
1954 1588
        if (isset($mapping['association'])) {
1955 1385
            $this->associationMappings[$mapping['fieldName']] = $mapping;
1956
        }
1957
1958 1588
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
1959 1587
        $reflProp->setAccessible(true);
1960 1587
        $this->reflFields[$mapping['fieldName']] = $reflProp;
1961
1962 1587
        return $mapping;
1963
    }
1964
1965
    /**
1966
     * Determines which fields get serialized.
1967
     *
1968
     * It is only serialized what is necessary for best unserialization performance.
1969
     * That means any metadata properties that are not set or empty or simply have
1970
     * their default value are NOT serialized.
1971
     *
1972
     * Parts that are also NOT serialized because they can not be properly unserialized:
1973
     *      - reflClass (ReflectionClass)
1974
     *      - reflFields (ReflectionProperty array)
1975
     *
1976
     * @return array The names of all the fields that should be serialized.
1977
     */
1978 6
    public function __sleep()
1979
    {
1980
        // This metadata is always serialized/cached.
1981
        $serialized = [
1982 6
            'fieldMappings',
1983
            'associationMappings',
1984
            'identifier',
1985
            'name',
1986
            'db',
1987
            'collection',
1988
            'readPreference',
1989
            'readPreferenceTags',
1990
            'writeConcern',
1991
            'rootDocumentName',
1992
            'generatorType',
1993
            'generatorOptions',
1994
            'idGenerator',
1995
            'indexes',
1996
            'shardKey',
1997
        ];
1998
1999
        // The rest of the metadata is only serialized if necessary.
2000 6
        if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {
2001
            $serialized[] = 'changeTrackingPolicy';
2002
        }
2003
2004 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...
2005 1
            $serialized[] = 'customRepositoryClassName';
2006
        }
2007
2008 6
        if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE || $this->discriminatorField !== null) {
2009 4
            $serialized[] = 'inheritanceType';
2010 4
            $serialized[] = 'discriminatorField';
2011 4
            $serialized[] = 'discriminatorValue';
2012 4
            $serialized[] = 'discriminatorMap';
2013 4
            $serialized[] = 'defaultDiscriminatorValue';
2014 4
            $serialized[] = 'parentClasses';
2015 4
            $serialized[] = 'subClasses';
2016
        }
2017
2018 6
        if ($this->isMappedSuperclass) {
2019 1
            $serialized[] = 'isMappedSuperclass';
2020
        }
2021
2022 6
        if ($this->isEmbeddedDocument) {
2023 1
            $serialized[] = 'isEmbeddedDocument';
2024
        }
2025
2026 6
        if ($this->isQueryResultDocument) {
2027
            $serialized[] = 'isQueryResultDocument';
2028
        }
2029
2030 6
        if ($this->isFile) {
2031
            $serialized[] = 'isFile';
2032
            $serialized[] = 'bucketName';
2033
            $serialized[] = 'chunkSizeBytes';
2034
        }
2035
2036 6
        if ($this->isVersioned) {
2037
            $serialized[] = 'isVersioned';
2038
            $serialized[] = 'versionField';
2039
        }
2040
2041 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...
2042
            $serialized[] = 'lifecycleCallbacks';
2043
        }
2044
2045 6
        if ($this->collectionCapped) {
2046 1
            $serialized[] = 'collectionCapped';
2047 1
            $serialized[] = 'collectionSize';
2048 1
            $serialized[] = 'collectionMax';
2049
        }
2050
2051 6
        if ($this->isReadOnly) {
2052
            $serialized[] = 'isReadOnly';
2053
        }
2054
2055 6
        return $serialized;
2056
    }
2057
2058
    /**
2059
     * Restores some state that can not be serialized/unserialized.
2060
     */
2061 6
    public function __wakeup()
2062
    {
2063
        // Restore ReflectionClass and properties
2064 6
        $this->reflClass    = new ReflectionClass($this->name);
2065 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...
2066
2067 6
        foreach ($this->fieldMappings as $field => $mapping) {
2068 3
            if (isset($mapping['declared'])) {
2069 1
                $reflField = new ReflectionProperty($mapping['declared'], $field);
2070
            } else {
2071 3
                $reflField = $this->reflClass->getProperty($field);
2072
            }
2073 3
            $reflField->setAccessible(true);
2074 3
            $this->reflFields[$field] = $reflField;
2075
        }
2076 6
    }
2077
2078
    /**
2079
     * Creates a new instance of the mapped class, without invoking the constructor.
2080
     */
2081 372
    public function newInstance() : object
2082
    {
2083 372
        return $this->instantiator->instantiate($this->name);
2084
    }
2085
2086 132
    private function isAllowedGridFSField(string $name) : bool
2087
    {
2088 132
        return in_array($name, self::ALLOWED_GRIDFS_FIELDS, true);
2089
    }
2090
2091 1588
    private function typeRequirementsAreMet(string $type) : void
2092
    {
2093 1588
        if ($type === Type::DECIMAL128 && !extension_loaded('bcmath')) {
2094
            throw MappingException::typeRequirementsNotFulfilled(Type::DECIMAL128, 'ext-bcmath is missing');
2095
        }
2096 1588
    }
2097
2098 1588
    private function checkDuplicateMapping(array $mapping) : void
2099
    {
2100 1588
        if ($mapping['notSaved'] ?? false) {
2101 967
            return;
2102
        }
2103
2104 1588
        foreach ($this->fieldMappings as $fieldName => $otherMapping) {
2105
            // Ignore fields with the same name - we can safely override their mapping
2106 1536
            if ($mapping['fieldName'] === $fieldName) {
2107 121
                continue;
2108
            }
2109
2110
            // Ignore fields with a different name in the database
2111 1532
            if ($mapping['name'] !== $otherMapping['name']) {
2112 1532
                continue;
2113
            }
2114
2115
            // If the other field is not saved, ignore it as well
2116 2
            if ($otherMapping['notSaved'] ?? false) {
2117
                continue;
2118
            }
2119
2120 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...
2121
        }
2122 1588
    }
2123
}
2124