Completed
Push — master ( 8fc9bf...b852d4 )
by Andreas
17s queued 12s
created

ClassMetadata::addIndex()   B

Complexity

Conditions 6
Paths 1

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 6.0131

Importance

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