Completed
Pull Request — master (#1790)
by Andreas
21:41
created

ClassMetadata::getIndexes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Mapping;
6
7
use Doctrine\Common\Persistence\Mapping\ClassMetadata as BaseClassMetadata;
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\Proxy\Proxy;
13
use Doctrine\ODM\MongoDB\Types\Type;
14
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
15
use InvalidArgumentException;
16
use MongoDB\BSON\ObjectId;
17
use function array_filter;
18
use function array_key_exists;
19
use function array_keys;
20
use function array_map;
21
use function array_pop;
22
use function call_user_func_array;
23
use function class_exists;
24
use function constant;
25
use function count;
26
use function get_class;
27
use function in_array;
28
use function is_array;
29
use function is_string;
30
use function is_subclass_of;
31
use function ltrim;
32
use function sprintf;
33
use function strlen;
34
use function strpos;
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
 */
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
    /**
177
     * READ-ONLY: The name of the mongo database the document is mapped to.
178
     * @var string
179
     */
180
    public $db;
181
182
    /**
183
     * READ-ONLY: The name of the mongo collection the document is mapped to.
184
     * @var string
185
     */
186
    public $collection;
187
188
    /**
189
     * READ-ONLY: If the collection should be a fixed size.
190
     * @var bool
191
     */
192
    public $collectionCapped;
193
194
    /**
195
     * READ-ONLY: If the collection is fixed size, its size in bytes.
196
     * @var int|null
197
     */
198
    public $collectionSize;
199
200
    /**
201
     * READ-ONLY: If the collection is fixed size, the maximum number of elements to store in the collection.
202
     * @var int|null
203
     */
204
    public $collectionMax;
205
206
    /**
207
     * READ-ONLY Describes how MongoDB clients route read operations to the members of a replica set.
208
     * @var string|int|null
209
     */
210
    public $readPreference;
211
212
    /**
213
     * READ-ONLY Associated with readPreference Allows to specify criteria so that your application can target read
214
     * operations to specific members, based on custom parameters.
215
     * @var string[][]|null
216
     */
217
    public $readPreferenceTags;
218
219
    /**
220
     * READ-ONLY: Describes the level of acknowledgement requested from MongoDB for write operations.
221
     * @var string|int|null
222
     */
223
    public $writeConcern;
224
225
    /**
226
     * READ-ONLY: The field name of the document identifier.
227
     * @var string|null
228
     */
229
    public $identifier;
230
231
    /**
232
     * READ-ONLY: The array of indexes for the document collection.
233
     * @var array
234
     */
235
    public $indexes = [];
236
237
    /**
238
     * READ-ONLY: Keys and options describing shard key. Only for sharded collections.
239
     * @var string|null
240
     */
241
    public $shardKey;
242
243
    /**
244
     * READ-ONLY: The name of the document class.
245
     * @var string
246
     */
247
    public $name;
248
249
    /**
250
     * READ-ONLY: The namespace the document class is contained in.
251
     *
252
     * @var string
253
     * @todo Not really needed. Usage could be localized.
254
     */
255
    public $namespace;
256
257
    /**
258
     * READ-ONLY: The name of the document class that is at the root of the mapped document inheritance
259
     * hierarchy. If the document is not part of a mapped inheritance hierarchy this is the same
260
     * as {@link $documentName}.
261
     *
262
     * @var string
263
     */
264
    public $rootDocumentName;
265
266
    /**
267
     * The name of the custom repository class used for the document class.
268
     * (Optional).
269
     *
270
     * @var string
271
     */
272
    public $customRepositoryClassName;
273
274
    /**
275
     * READ-ONLY: The names of the parent classes (ancestors).
276
     *
277
     * @var array
278
     */
279
    public $parentClasses = [];
280
281
    /**
282
     * READ-ONLY: The names of all subclasses (descendants).
283
     *
284
     * @var array
285
     */
286
    public $subClasses = [];
287
288
    /**
289
     * The ReflectionProperty instances of the mapped class.
290
     *
291
     * @var \ReflectionProperty[]
292
     */
293
    public $reflFields = [];
294
295
    /**
296
     * READ-ONLY: The inheritance mapping type used by the class.
297
     *
298
     * @var int
299
     */
300
    public $inheritanceType = self::INHERITANCE_TYPE_NONE;
301
302
    /**
303
     * READ-ONLY: The Id generator type used by the class.
304
     *
305
     * @var string
306
     */
307
    public $generatorType = self::GENERATOR_TYPE_AUTO;
308
309
    /**
310
     * READ-ONLY: The Id generator options.
311
     *
312
     * @var array
313
     */
314
    public $generatorOptions = [];
315
316
    /**
317
     * READ-ONLY: The ID generator used for generating IDs for this class.
318
     *
319
     * @var AbstractIdGenerator
320
     */
321
    public $idGenerator;
322
323
    /**
324
     * READ-ONLY: The field mappings of the class.
325
     * Keys are field names and values are mapping definitions.
326
     *
327
     * The mapping definition array has the following values:
328
     *
329
     * - <b>fieldName</b> (string)
330
     * The name of the field in the Document.
331
     *
332
     * - <b>id</b> (boolean, optional)
333
     * Marks the field as the primary key of the document. Multiple fields of an
334
     * document can have the id attribute, forming a composite key.
335
     *
336
     * @var array
337
     */
338
    public $fieldMappings = [];
339
340
    /**
341
     * READ-ONLY: The association mappings of the class.
342
     * Keys are field names and values are mapping definitions.
343
     *
344
     * @var array
345
     */
346
    public $associationMappings = [];
347
348
    /**
349
     * READ-ONLY: Array of fields to also load with a given method.
350
     *
351
     * @var array
352
     */
353
    public $alsoLoadMethods = [];
354
355
    /**
356
     * READ-ONLY: The registered lifecycle callbacks for documents of this class.
357
     *
358
     * @var array
359
     */
360
    public $lifecycleCallbacks = [];
361
362
    /**
363
     * READ-ONLY: The discriminator value of this class.
364
     *
365
     * <b>This does only apply to the JOINED and SINGLE_COLLECTION inheritance mapping strategies
366
     * where a discriminator field is used.</b>
367
     *
368
     * @var mixed
369
     * @see discriminatorField
370
     */
371
    public $discriminatorValue;
372
373
    /**
374
     * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
375
     *
376
     * <b>This does only apply to the SINGLE_COLLECTION inheritance mapping strategy
377
     * where a discriminator field is used.</b>
378
     *
379
     * @var mixed
380
     * @see discriminatorField
381
     */
382
    public $discriminatorMap = [];
383
384
    /**
385
     * READ-ONLY: The definition of the discriminator field used in SINGLE_COLLECTION
386
     * inheritance mapping.
387
     *
388
     * @var string
389
     */
390
    public $discriminatorField;
391
392
    /**
393
     * READ-ONLY: The default value for discriminatorField in case it's not set in the document
394
     *
395
     * @var string
396
     * @see discriminatorField
397
     */
398
    public $defaultDiscriminatorValue;
399
400
    /**
401
     * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
402
     *
403
     * @var bool
404
     */
405
    public $isMappedSuperclass = false;
406
407
    /**
408
     * READ-ONLY: Whether this class describes the mapping of a embedded document.
409
     *
410
     * @var bool
411
     */
412
    public $isEmbeddedDocument = false;
413
414
    /**
415
     * READ-ONLY: Whether this class describes the mapping of an aggregation result document.
416
     *
417
     * @var bool
418
     */
419
    public $isQueryResultDocument = false;
420
421
    /**
422
     * READ-ONLY: Whether this class describes the mapping of a gridFS file
423
     *
424
     * @var bool
425
     */
426
    public $isFile = false;
427
428
    /**
429
     * READ-ONLY: The policy used for change-tracking on entities of this class.
430
     *
431
     * @var int
432
     */
433
    public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;
434
435
    /**
436
     * READ-ONLY: A flag for whether or not instances of this class are to be versioned
437
     * with optimistic locking.
438
     *
439
     * @var bool $isVersioned
440
     */
441
    public $isVersioned;
442
443
    /**
444
     * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
445
     *
446
     * @var mixed $versionField
447
     */
448
    public $versionField;
449
450
    /**
451
     * READ-ONLY: A flag for whether or not instances of this class are to allow pessimistic
452
     * locking.
453
     *
454
     * @var bool $isLockable
455
     */
456
    public $isLockable;
457
458
    /**
459
     * READ-ONLY: The name of the field which is used for locking a document.
460
     *
461
     * @var mixed $lockField
462
     */
463
    public $lockField;
464
465
    /**
466
     * The ReflectionClass instance of the mapped class.
467
     *
468
     * @var \ReflectionClass
469
     */
470
    public $reflClass;
471
472
    /**
473
     * READ_ONLY: A flag for whether or not this document is read-only.
474
     *
475
     * @var bool
476
     */
477
    public $isReadOnly;
478
479
    /** @var InstantiatorInterface|null */
480
    private $instantiator;
481 1478
482
    /**
483 1478
     * Initializes a new ClassMetadata instance that will hold the object-document mapping
484 1478
     * metadata of the class with the given name.
485 1478
     *
486 1478
     * @param string $documentName The name of the document class the new instance is used for.
487 1478
     */
488 1478
    public function __construct($documentName)
489 1478
    {
490
        $this->name = $documentName;
491
        $this->rootDocumentName = $documentName;
492
        $this->reflClass = new \ReflectionClass($documentName);
493
        $this->namespace = $this->reflClass->getNamespaceName();
494
        $this->setCollection($this->reflClass->getShortName());
495
        $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...antiatorInterface>|null 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...
496
    }
497
498 121
    /**
499
     * Helper method to get reference id of ref* type references
500 121
     * @param mixed  $reference
501
     * @param string $storeAs
502
     * @return mixed
503
     * @internal
504
     */
505
    public static function getReferenceId($reference, $storeAs)
506
    {
507
        return $storeAs === self::REFERENCE_STORE_AS_ID ? $reference : $reference[self::getReferencePrefix($storeAs) . 'id'];
508 186
    }
509
510 186
    /**
511
     * Returns the reference prefix used for a reference
512
     * @param string $storeAs
513
     * @return string
514 186
     */
515
    private static function getReferencePrefix($storeAs)
516
    {
517
        if (! in_array($storeAs, [self::REFERENCE_STORE_AS_REF, self::REFERENCE_STORE_AS_DB_REF, self::REFERENCE_STORE_AS_DB_REF_WITH_DB])) {
518
            throw new \LogicException('Can only get a reference prefix for DBRef and reference arrays');
519
        }
520
521
        return $storeAs === self::REFERENCE_STORE_AS_REF ? '' : '$';
522
    }
523
524 134
    /**
525
     * Returns a fully qualified field name for a given reference
526 134
     * @param string $storeAs
527 94
     * @param string $pathPrefix The field path prefix
528
     * @return string
529
     * @internal
530 122
     */
531
    public static function getReferenceFieldName($storeAs, $pathPrefix = '')
532
    {
533
        if ($storeAs === self::REFERENCE_STORE_AS_ID) {
534
            return $pathPrefix;
535
        }
536 1366
537
        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...
538 1366
    }
539
540
    /**
541
     * {@inheritDoc}
542 1366
     */
543
    public function getReflectionClass()
544
    {
545
        if (! $this->reflClass) {
546
            $this->reflClass = new \ReflectionClass($this->name);
547
        }
548 322
549
        return $this->reflClass;
550 322
    }
551
552
    /**
553
     * {@inheritDoc}
554
     */
555
    public function isIdentifier($fieldName)
556
    {
557
        return $this->identifier === $fieldName;
558
    }
559 887
560
    /**
561 887
     * INTERNAL:
562 887
     * Sets the mapped identifier field of this class.
563
     *
564
     * @param string $identifier
565
     */
566
    public function setIdentifier($identifier)
567
    {
568
        $this->identifier = $identifier;
569
    }
570 39
571
    /**
572 39
     * {@inheritDoc}
573
     *
574
     * Since MongoDB only allows exactly one identifier field
575
     * this will always return an array with only one value
576
     */
577
    public function getIdentifier()
578
    {
579
        return [$this->identifier];
580
    }
581 98
582
    /**
583 98
     * {@inheritDoc}
584
     *
585
     * Since MongoDB only allows exactly one identifier field
586
     * this will always return an array with only one value
587
     */
588
    public function getIdentifierFieldNames()
589 887
    {
590
        return [$this->identifier];
591 887
    }
592
593
    /**
594
     * {@inheritDoc}
595
     */
596
    public function hasField($fieldName)
597
    {
598
        return isset($this->fieldMappings[$fieldName]);
599 903
    }
600
601 903
    /**
602 903
     * Sets the inheritance type used by the class and it's subclasses.
603
     *
604
     * @param int $type
605
     */
606
    public function setInheritanceType($type)
607
    {
608
        $this->inheritanceType = $type;
609
    }
610
611 1362
    /**
612
     * Checks whether a mapped field is inherited from an entity superclass.
613 1362
     *
614
     * @param  string $fieldName
615
     *
616
     * @return bool TRUE if the field is inherited, FALSE otherwise.
617
     */
618
    public function isInheritedField($fieldName)
619
    {
620
        return isset($this->fieldMappings[$fieldName]['inherited']);
621 835
    }
622
623 835
    /**
624
     * Registers a custom repository class for the document class.
625
     *
626
     * @param string $repositoryClassName The class name of the custom repository.
627 835
     */
628 3
    public function setCustomRepositoryClass($repositoryClassName)
629
    {
630
        if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
631 835
            return;
632 835
        }
633
634
        if ($repositoryClassName && strpos($repositoryClassName, '\\') === false && strlen($this->namespace)) {
635
            $repositoryClassName = $this->namespace . '\\' . $repositoryClassName;
636
        }
637
638
        $this->customRepositoryClassName = $repositoryClassName;
639
    }
640
641
    /**
642
     * Dispatches the lifecycle event of the given document by invoking all
643
     * registered callbacks.
644 599
     *
645
     * @param string $event     Lifecycle event
646 599
     * @param object $document  Document on which the event occurred
647 1
     * @param array  $arguments Arguments to pass to all callbacks
648
     * @throws \InvalidArgumentException If document class is not this class or
649
     *                                   a Proxy of this class.
650 598
     */
651 583
    public function invokeLifecycleCallbacks($event, $document, ?array $arguments = null)
652
    {
653
        if (! $document instanceof $this->name) {
654 176
            throw new \InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"', $this->name, get_class($document)));
655 176
        }
656 175
657
        if (empty($this->lifecycleCallbacks[$event])) {
658 176
            return;
659
        }
660
661 176
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
662
            if ($arguments !== null) {
663
                call_user_func_array([$document, $callback], $arguments);
664
            } else {
665
                $document->$callback();
666
            }
667
        }
668
    }
669
670
    /**
671
     * Checks whether the class has callbacks registered for a lifecycle event.
672
     *
673
     * @param string $event Lifecycle event
674
     *
675
     * @return bool
676
     */
677
    public function hasLifecycleCallbacks($event)
678
    {
679
        return ! empty($this->lifecycleCallbacks[$event]);
680
    }
681
682
    /**
683
     * Gets the registered lifecycle callbacks for an event.
684
     *
685
     * @param string $event
686
     * @return array
687
     */
688
    public function getLifecycleCallbacks($event)
689
    {
690
        return $this->lifecycleCallbacks[$event] ?? [];
691
    }
692
693
    /**
694 802
     * Adds a lifecycle callback for documents of this class.
695
     *
696 802
     * If the callback is already registered, this is a NOOP.
697 1
     *
698
     * @param string $callback
699
     * @param string $event
700 802
     */
701 802
    public function addLifecycleCallback($callback, $event)
702
    {
703
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
704
            return;
705
        }
706
707
        $this->lifecycleCallbacks[$event][] = $callback;
708
    }
709
710 886
    /**
711
     * Sets the lifecycle callbacks for documents of this class.
712 886
     *
713 886
     * Any previously registered callbacks are overwritten.
714
     *
715
     * @param array $callbacks
716
     */
717
    public function setLifecycleCallbacks(array $callbacks)
718
    {
719
        $this->lifecycleCallbacks = $callbacks;
720
    }
721
722
    /**
723
     * Registers a method for loading document data before field hydration.
724 14
     *
725
     * Note: A method may be registered multiple times for different fields.
726 14
     * it will be invoked only once for the first field found.
727 14
     *
728
     * @param string       $method Method name
729
     * @param array|string $fields Database field name(s)
730
     */
731
    public function registerAlsoLoadMethod($method, $fields)
732
    {
733
        $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : [$fields];
734
    }
735
736 886
    /**
737
     * Sets the AlsoLoad methods for documents of this class.
738 886
     *
739 886
     * Any previously registered methods are overwritten.
740
     *
741
     * @param array $methods
742
     */
743
    public function setAlsoLoadMethods(array $methods)
744
    {
745
        $this->alsoLoadMethods = $methods;
746
    }
747
748
    /**
749
     * Sets the discriminator field.
750
     *
751
     * The field name is the the unmapped database field. Discriminator values
752
     * are only used to discern the hydration class and are not mapped to class
753 912
     * properties.
754
     *
755 912
     * @param string $discriminatorField
756 844
     *
757
     * @throws MappingException If the discriminator field conflicts with the
758 844
     *                          "name" attribute of a mapped field.
759
     */
760
    public function setDiscriminatorField($discriminatorField)
761
    {
762 117
        if ($discriminatorField === null) {
763
            $this->discriminatorField = null;
764
765
            return;
766
        }
767
768
        if ($this->isFile) {
769
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
770 117
        }
771 4
772 4
        // Handle array argument with name/fieldName keys for BC
773
        if (is_array($discriminatorField)) {
774
            if (isset($discriminatorField['name'])) {
775
                $discriminatorField = $discriminatorField['name'];
776 116
            } elseif (isset($discriminatorField['fieldName'])) {
777 116
                $discriminatorField = $discriminatorField['fieldName'];
778
            }
779
        }
780
781
        foreach ($this->fieldMappings as $fieldMapping) {
782
            if ($discriminatorField === $fieldMapping['name']) {
783
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
784
            }
785
        }
786
787 905
        $this->discriminatorField = $discriminatorField;
788
    }
789 905
790 112
    /**
791 82
     * Sets the discriminator values used by this class.
792
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
793 112
     *
794 112
     * @param array $map
795 104
     *
796
     * @throws MappingException
797 111
     */
798
    public function setDiscriminatorMap(array $map)
799
    {
800 111
        if ($this->isFile) {
801 112
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
802
        }
803
804
        foreach ($map as $value => $className) {
805 905
            if (strpos($className, '\\') === false && strlen($this->namespace)) {
806
                $className = $this->namespace . '\\' . $className;
807
            }
808
            $this->discriminatorMap[$value] = $className;
809
            if ($this->name === $className) {
810
                $this->discriminatorValue = $value;
811
            } else {
812
                if (! class_exists($className)) {
813
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
814
                }
815 889
                if (is_subclass_of($className, $this->name)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $this->name can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
816
                    $this->subClasses[] = $className;
817 889
                }
818 886
            }
819
        }
820 886
    }
821
822
    /**
823 48
     * Sets the default discriminator value to be used for this class
824
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies if the document has no discriminator value
825
     *
826
     * @param string $defaultDiscriminatorValue
827 48
     *
828 48
     * @throws MappingException
829
     */
830
    public function setDefaultDiscriminatorValue($defaultDiscriminatorValue)
831
    {
832
        if ($defaultDiscriminatorValue === null) {
833
            $this->defaultDiscriminatorValue = null;
834
835
            return;
836
        }
837 3
838
        if ($this->isFile) {
839 3
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
840 3
        }
841 3
842
        if (! array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
843
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
844
        }
845
846
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
847
    }
848
849 183
    /**
850
     * Sets the discriminator value for this class.
851 183
     * Used for JOINED/SINGLE_TABLE inheritance and multiple document types in a single
852
     * collection.
853 183
     *
854 45
     * @param string $value
855
     *
856 183
     * @throws MappingException
857 183
     */
858 183
    public function setDiscriminatorValue($value)
859 176
    {
860
        if ($this->isFile) {
861
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
862 52
        }
863
864
        $this->discriminatorMap[$value] = $this->name;
865
        $this->discriminatorValue = $value;
866 52
    }
867 183
868 183
    /**
869
     * Add a index for this Document.
870 183
     *
871
     * @param array $keys    Array of keys for the index.
872
     * @param array $options Array of options for the index.
873
     */
874
    public function addIndex($keys, array $options = [])
875
    {
876
        $this->indexes[] = [
877 23
            'keys' => array_map(function ($value) {
878
                if ($value === 1 || $value === -1) {
879 23
                    return (int) $value;
880
                }
881
                if (is_string($value)) {
882
                    $lower = strtolower($value);
883
                    if ($lower === 'asc') {
884
                        return 1;
885
                    }
886
887
                    if ($lower === 'desc') {
888
                        return -1;
889
                    }
890
                }
891
                return $value;
892
            }, $keys),
893
            'options' => $options,
894
        ];
895
    }
896
897
    /**
898
     * Returns the array of indexes for this Document.
899
     *
900 71
     * @return array $indexes The array of indexes.
901
     */
902 71
    public function getIndexes()
903 2
    {
904
        return $this->indexes;
905
    }
906 71
907 2
    /**
908
     * Checks whether this document has indexes or not.
909
     *
910 69
     * @return bool
911 69
     */
912 62
    public function hasIndexes()
913
    {
914
        return $this->indexes ? true : false;
915 7
    }
916 3
917
    /**
918
     * Set shard key for this Document.
919 4
     *
920 4
     * @param array $keys    Array of document keys.
921
     * @param array $options Array of sharding options.
922
     *
923
     * @throws MappingException
924 65
     */
925
    public function setShardKey(array $keys, array $options = [])
926 65
    {
927 5
        if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && $this->shardKey !== null) {
928
            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...
929 65
        }
930 65
931 65
        if ($this->isEmbeddedDocument) {
932 63
            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...
933
        }
934
935 47
        foreach (array_keys($keys) as $field) {
936
            if (! isset($this->fieldMappings[$field])) {
937
                continue;
938
            }
939 47
940 65
            if (in_array($this->fieldMappings[$field]['type'], ['many', 'collection'])) {
941 65
                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...
942
            }
943 65
944
            if ($this->fieldMappings[$field]['strategy'] !== static::STORAGE_STRATEGY_SET) {
945
                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...
946
            }
947
        }
948 17
949
        $this->shardKey = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array('keys' => \array_m... 'options' => $options) of type array<string,array,{"key...ay","options":"array"}> is incompatible with the declared type string|null of property $shardKey.

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...
950 17
            'keys' => array_map(function ($value) {
951
                if ($value === 1 || $value === -1) {
952
                    return (int) $value;
953
                }
954
                if (is_string($value)) {
955
                    $lower = strtolower($value);
956
                    if ($lower === 'asc') {
957
                        return 1;
958 1096
                    }
959
960 1096
                    if ($lower === 'desc') {
961
                        return -1;
962
                    }
963
                }
964
                return $value;
965
            }, $keys),
966
            'options' => $options,
967
        ];
968
    }
969 886
970
    /**
971 886
     * @return array
972 886
     */
973 886
    public function getShardKey()
974
    {
975
        return $this->shardKey;
976
    }
977
978
    /**
979
     * Checks whether this document has shard key or not.
980 896
     *
981
     * @return bool
982 896
     */
983 896
    public function isSharded()
984
    {
985
        return $this->shardKey ? true : false;
986
    }
987
988 11
    /**
989
     * Sets the read preference used by this class.
990 11
     *
991
     * @param string     $readPreference
992
     * @param array|null $tags
993
     */
994
    public function setReadPreference($readPreference, $tags)
995
    {
996
        $this->readPreference = $readPreference;
997
        $this->readPreferenceTags = $tags;
998 548
    }
999
1000 548
    /**
1001
     * Sets the write concern used by this class.
1002
     *
1003
     * @param string $writeConcern
1004
     */
1005
    public function setWriteConcern($writeConcern)
1006
    {
1007
        $this->writeConcern = $writeConcern;
1008 888
    }
1009
1010 888
    /**
1011 888
     * @return string
1012
     */
1013
    public function getWriteConcern()
1014
    {
1015
        return $this->writeConcern;
1016
    }
1017
1018 62
    /**
1019
     * Whether there is a write concern configured for this class.
1020 62
     *
1021
     * @return bool
1022
     */
1023
    public function hasWriteConcern()
1024
    {
1025
        return $this->writeConcern !== null;
1026
    }
1027
1028 568
    /**
1029
     * Sets the change tracking policy used by this class.
1030 568
     *
1031
     * @param int $policy
1032
     */
1033
    public function setChangeTrackingPolicy($policy)
1034
    {
1035
        $this->changeTrackingPolicy = $policy;
1036
    }
1037
1038 309
    /**
1039
     * Whether the change tracking policy of this class is "deferred explicit".
1040 309
     *
1041
     * @return bool
1042
     */
1043
    public function isChangeTrackingDeferredExplicit()
1044
    {
1045
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT;
1046
    }
1047
1048 98
    /**
1049
     * Whether the change tracking policy of this class is "deferred implicit".
1050 98
     *
1051
     * @return bool
1052
     */
1053
    public function isChangeTrackingDeferredImplicit()
1054
    {
1055
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT;
1056
    }
1057
1058
    /**
1059
     * Whether the change tracking policy of this class is "notify".
1060
     *
1061
     * @return bool
1062
     */
1063
    public function isChangeTrackingNotify()
1064
    {
1065
        return $this->changeTrackingPolicy === self::CHANGETRACKING_NOTIFY;
1066
    }
1067
1068 1371
    /**
1069
     * Gets the ReflectionProperties of the mapped class.
1070 1371
     *
1071
     * @return array An array of ReflectionProperty instances.
1072
     */
1073
    public function getReflectionProperties()
1074
    {
1075
        return $this->reflFields;
1076
    }
1077
1078
    /**
1079
     * Gets a ReflectionProperty for a specific field of the mapped class.
1080
     *
1081
     * @param string $name
1082
     *
1083
     * @return \ReflectionProperty
1084
     */
1085
    public function getReflectionProperty($name)
1086
    {
1087
        return $this->reflFields[$name];
1088 1295
    }
1089
1090 1295
    /**
1091
     * {@inheritDoc}
1092
     */
1093
    public function getName()
1094
    {
1095
        return $this->name;
1096
    }
1097
1098 92
    /**
1099
     * The namespace this Document class belongs to.
1100 92
     *
1101 92
     * @return string $namespace The namespace name.
1102
     */
1103
    public function getNamespace()
1104
    {
1105
        return $this->namespace;
1106
    }
1107
1108 1296
    /**
1109
     * Returns the database this Document is mapped to.
1110 1296
     *
1111
     * @return string $db The database name.
1112
     */
1113
    public function getDatabase()
1114
    {
1115
        return $this->db;
1116
    }
1117
1118
    /**
1119
     * Set the database this Document is mapped to.
1120 1478
     *
1121
     * @param string $db The database name
1122 1478
     */
1123 1
    public function setDatabase($db)
1124
    {
1125
        $this->db = $db;
1126 1
    }
1127 1
1128 1
    /**
1129 1
     * Get the collection this Document is mapped to.
1130
     *
1131 1478
     * @return string $collection The collection name.
1132
     */
1133 1478
    public function getCollection()
1134
    {
1135
        return $this->collection;
1136
    }
1137
1138
    /**
1139
     * Sets the collection this Document is mapped to.
1140 5
     *
1141
     * @param array|string $name
1142 5
     *
1143
     * @throws \InvalidArgumentException
1144
     */
1145
    public function setCollection($name)
1146
    {
1147
        if (is_array($name)) {
1148
            if (! isset($name['name'])) {
1149
                throw new \InvalidArgumentException('A name key is required when passing an array to setCollection()');
1150 1
            }
1151
            $this->collectionCapped = $name['capped'] ?? false;
1152 1
            $this->collectionSize = $name['size'] ?? 0;
1153 1
            $this->collectionMax = $name['max'] ?? 0;
1154
            $this->collection = $name['name'];
1155
        } else {
1156
            $this->collection = $name;
1157
        }
1158
    }
1159
1160 5
    /**
1161
     * Get whether or not the documents collection is capped.
1162 5
     *
1163
     * @return bool
1164
     */
1165
    public function getCollectionCapped()
1166
    {
1167
        return $this->collectionCapped;
1168
    }
1169
1170 1
    /**
1171
     * Set whether or not the documents collection is capped.
1172 1
     *
1173 1
     * @param bool $bool
1174
     */
1175
    public function setCollectionCapped($bool)
1176
    {
1177
        $this->collectionCapped = $bool;
1178
    }
1179
1180 5
    /**
1181
     * Get the collection size
1182 5
     *
1183
     * @return int
1184
     */
1185
    public function getCollectionSize()
1186
    {
1187
        return $this->collectionSize;
1188
    }
1189
1190 1
    /**
1191
     * Set the collection size.
1192 1
     *
1193 1
     * @param int $size
1194
     */
1195
    public function setCollectionSize($size)
1196
    {
1197
        $this->collectionSize = $size;
1198
    }
1199
1200
    /**
1201
     * Get the collection max.
1202
     *
1203
     * @return int
1204
     */
1205
    public function getCollectionMax()
1206
    {
1207
        return $this->collectionMax;
1208
    }
1209
1210 1388
    /**
1211
     * Set the collection max.
1212 1388
     *
1213 1370
     * @param int $max
1214
     */
1215
    public function setCollectionMax($max)
1216
    {
1217 1353
        $this->collectionMax = $max;
1218 1352
    }
1219 819
1220 819
    /**
1221 819
     * Returns TRUE if this Document is mapped to a collection FALSE otherwise.
1222
     *
1223 1352
     * @return bool
1224 1076
     */
1225
    public function isMappedToCollection()
1226 1076
    {
1227 1076
        return $this->collection ? true : false;
1228 1076
    }
1229 1076
1230 1076
    /**
1231 1076
     * Validates the storage strategy of a mapping for consistency
1232
     * @param array $mapping
1233 1076
     * @throws MappingException
1234
     */
1235
    private function applyStorageStrategy(array &$mapping)
1236 1339
    {
1237 1339
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1238
            return;
1239
        }
1240 1353
1241 1344
        switch (true) {
1242
            case $mapping['type'] === 'int':
1243
            case $mapping['type'] === 'float':
1244 1353
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1245
                $allowedStrategies = [self::STORAGE_STRATEGY_SET, self::STORAGE_STRATEGY_INCREMENT];
1246
                break;
1247
1248 1353
            case $mapping['type'] === 'many':
1249 1353
                $defaultStrategy = CollectionHelper::DEFAULT_STRATEGY;
1250 1
                $allowedStrategies = [
1251
                    self::STORAGE_STRATEGY_PUSH_ALL,
1252 1352
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1253
                    self::STORAGE_STRATEGY_SET,
1254
                    self::STORAGE_STRATEGY_SET_ARRAY,
1255
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1256
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1257
                ];
1258
                break;
1259 6
1260
            default:
1261 6
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1262 6
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1263 6
        }
1264 5
1265
        if (! isset($mapping['strategy'])) {
1266
            $mapping['strategy'] = $defaultStrategy;
1267
        }
1268
1269
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1270
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1271 5
        }
1272
1273 5
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1274 5
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1275 5
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1276 5
        }
1277
    }
1278
1279
    /**
1280
     * Map a single embedded document.
1281
     *
1282
     * @param array $mapping The mapping information.
1283 2
     */
1284
    public function mapOneEmbedded(array $mapping)
1285 2
    {
1286 2
        $mapping['embedded'] = true;
1287 2
        $mapping['type'] = 'one';
1288 2
        $this->mapField($mapping);
1289
    }
1290
1291
    /**
1292
     * Map a collection of embedded documents.
1293
     *
1294
     * @param array $mapping The mapping information.
1295 1
     */
1296
    public function mapManyEmbedded(array $mapping)
1297 1
    {
1298 1
        $mapping['embedded'] = true;
1299 1
        $mapping['type'] = 'many';
1300 1
        $this->mapField($mapping);
1301
    }
1302
1303
    /**
1304
     * Map a single document reference.
1305
     *
1306
     * @param array $mapping The mapping information.
1307
     */
1308
    public function mapOneReference(array $mapping)
1309 116
    {
1310
        $mapping['reference'] = true;
1311 116
        $mapping['type'] = 'one';
1312
        $this->mapField($mapping);
1313 116
    }
1314 116
1315
    /**
1316
     * Map a collection of document references.
1317 67
     *
1318 67
     * @param array $mapping The mapping information.
1319
     */
1320
    public function mapManyReference(array $mapping)
1321
    {
1322
        $mapping['reference'] = true;
1323
        $mapping['type'] = 'many';
1324
        $this->mapField($mapping);
1325
    }
1326
1327
    /**
1328
     * INTERNAL:
1329
     * Adds a field mapping without completing/validating it.
1330 68
     * This is mainly used to add inherited field mappings to derived classes.
1331
     *
1332 68
     * @param array $fieldMapping
1333 68
     */
1334
    public function addInheritedFieldMapping(array $fieldMapping)
1335
    {
1336
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1337
1338
        if (! isset($fieldMapping['association'])) {
1339
            return;
1340
        }
1341 32
1342
        $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1343 32
    }
1344
1345
    /**
1346
     * INTERNAL:
1347
     * Adds an association mapping without completing/validating it.
1348
     * This is mainly used to add inherited association mappings to derived classes.
1349
     *
1350
     * @param array $mapping
1351
     *
1352 5
     *
1353
     * @throws MappingException
1354 5
     */
1355
    public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
1356
    {
1357
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1358
    }
1359
1360
    /**
1361
     * Checks whether the class has a mapped association with the given field name.
1362 7
     *
1363
     * @param string $fieldName
1364 7
     * @return bool
1365
     */
1366
    public function hasReference($fieldName)
1367
    {
1368
        return isset($this->fieldMappings[$fieldName]['reference']);
1369
    }
1370
1371
    /**
1372
     * Checks whether the class has a mapped embed with the given field name.
1373
     *
1374
     * @param string $fieldName
1375
     * @return bool
1376
     */
1377
    public function hasEmbed($fieldName)
1378
    {
1379
        return isset($this->fieldMappings[$fieldName]['embedded']);
1380
    }
1381
1382
    /**
1383
     * {@inheritDoc}
1384
     *
1385
     * Checks whether the class has a mapped association (embed or reference) with the given field name.
1386
     */
1387
    public function hasAssociation($fieldName)
1388
    {
1389
        return $this->hasReference($fieldName) || $this->hasEmbed($fieldName);
1390
    }
1391
1392
    /**
1393
     * {@inheritDoc}
1394
     *
1395
     * Checks whether the class has a mapped reference or embed for the specified field and
1396
     * is a single valued association.
1397
     */
1398
    public function isSingleValuedAssociation($fieldName)
1399
    {
1400
        return $this->isSingleValuedReference($fieldName) || $this->isSingleValuedEmbed($fieldName);
1401
    }
1402
1403
    /**
1404
     * {@inheritDoc}
1405
     *
1406
     * Checks whether the class has a mapped reference or embed for the specified field and
1407
     * is a collection valued association.
1408
     */
1409
    public function isCollectionValuedAssociation($fieldName)
1410
    {
1411
        return $this->isCollectionValuedReference($fieldName) || $this->isCollectionValuedEmbed($fieldName);
1412
    }
1413
1414
    /**
1415
     * Checks whether the class has a mapped association for the specified field
1416
     * and if yes, checks whether it is a single-valued association (to-one).
1417
     *
1418
     * @param string $fieldName
1419
     * @return bool TRUE if the association exists and is single-valued, FALSE otherwise.
1420
     */
1421
    public function isSingleValuedReference($fieldName)
1422
    {
1423
        return isset($this->fieldMappings[$fieldName]['association']) &&
1424
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_ONE;
1425
    }
1426
1427
    /**
1428
     * Checks whether the class has a mapped association for the specified field
1429
     * and if yes, checks whether it is a collection-valued association (to-many).
1430
     *
1431
     * @param string $fieldName
1432
     * @return bool TRUE if the association exists and is collection-valued, FALSE otherwise.
1433
     */
1434
    public function isCollectionValuedReference($fieldName)
1435
    {
1436
        return isset($this->fieldMappings[$fieldName]['association']) &&
1437
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_MANY;
1438
    }
1439
1440
    /**
1441
     * Checks whether the class has a mapped embedded document for the specified field
1442
     * and if yes, checks whether it is a single-valued association (to-one).
1443
     *
1444
     * @param string $fieldName
1445
     * @return bool TRUE if the association exists and is single-valued, FALSE otherwise.
1446 1307
     */
1447
    public function isSingleValuedEmbed($fieldName)
1448 1307
    {
1449 1307
        return isset($this->fieldMappings[$fieldName]['association']) &&
1450
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_ONE;
1451
    }
1452
1453
    /**
1454
     * Checks whether the class has a mapped embedded document for the specified field
1455
     * and if yes, checks whether it is a collection-valued association (to-many).
1456
     *
1457 591
     * @param string $fieldName
1458
     * @return bool TRUE if the association exists and is collection-valued, FALSE otherwise.
1459 591
     */
1460 591
    public function isCollectionValuedEmbed($fieldName)
1461
    {
1462
        return isset($this->fieldMappings[$fieldName]['association']) &&
1463
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_MANY;
1464
    }
1465
1466
    /**
1467
     * Sets the ID generator used to generate IDs for instances of this class.
1468
     *
1469 655
     * @param AbstractIdGenerator $generator
1470
     */
1471 655
    public function setIdGenerator($generator)
1472 655
    {
1473
        $this->idGenerator = $generator;
1474
    }
1475
1476
    /**
1477
     * Casts the identifier to its portable PHP type.
1478
     *
1479
     * @param mixed $id
1480
     * @return mixed $id
1481
     */
1482
    public function getPHPIdentifierValue($id)
1483 520
    {
1484
        $idType = $this->fieldMappings[$this->identifier]['type'];
1485 520
        return Type::getType($idType)->convertToPHPValue($id);
1486 520
    }
1487 520
1488
    /**
1489
     * Casts the identifier to its database type.
1490
     *
1491
     * @param mixed $id
1492
     * @return mixed $id
1493
     */
1494
    public function getDatabaseIdentifierValue($id)
1495 603
    {
1496
        $idType = $this->fieldMappings[$this->identifier]['type'];
1497 603
        return Type::getType($idType)->convertToDatabaseValue($id);
1498
    }
1499
1500
    /**
1501
     * Sets the document identifier of a document.
1502
     *
1503
     * The value will be converted to a PHP type before being set.
1504
     *
1505
     * @param object $document
1506
     * @param mixed  $id
1507
     */
1508
    public function setIdentifierValue($document, $id)
1509
    {
1510
        $id = $this->getPHPIdentifierValue($id);
1511
        $this->reflFields[$this->identifier]->setValue($document, $id);
1512
    }
1513
1514
    /**
1515
     * Gets the document identifier as a PHP type.
1516
     *
1517
     * @param object $document
1518
     * @return mixed $id
1519 31
     */
1520
    public function getIdentifierValue($document)
1521 31
    {
1522
        return $this->reflFields[$this->identifier]->getValue($document);
1523
    }
1524
1525
    /**
1526
     * {@inheritDoc}
1527
     *
1528
     * Since MongoDB only allows exactly one identifier field this is a proxy
1529
     * to {@see getIdentifierValue()} and returns an array with the identifier
1530
     * field as a key.
1531 8
     */
1532
    public function getIdentifierValues($object)
1533 8
    {
1534
        return [$this->identifier => $this->getIdentifierValue($object)];
1535
    }
1536 1
1537
    /**
1538
     * Get the document identifier object as a database type.
1539 8
     *
1540 8
     * @param object $document
1541
     *
1542
     * @return ObjectId $id The ObjectId
1543
     */
1544
    public function getIdentifierObject($document)
1545
    {
1546
        return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));
1547
    }
1548
1549
    /**
1550 27
     * Sets the specified field to the specified value on the given document.
1551
     *
1552 27
     * @param object $document
1553 1
     * @param string $field
1554
     * @param mixed  $value
1555
     */
1556 27
    public function setFieldValue($document, $field, $value)
1557
    {
1558
        if ($document instanceof Proxy && ! $document->__isInitialized()) {
1559
            //property changes to an uninitialized proxy will not be tracked or persisted,
1560
            //so the proxy needs to be loaded first.
1561
            $document->__load();
1562
        }
1563
1564
        $this->reflFields[$field]->setValue($document, $value);
1565
    }
1566
1567
    /**
1568 167
     * Gets the specified field's value off the given document.
1569
     *
1570 167
     * @param object $document
1571 6
     * @param string $field
1572
     *
1573 165
     * @return mixed
1574
     */
1575
    public function getFieldValue($document, $field)
1576
    {
1577
        if ($document instanceof Proxy && $field !== $this->identifier && ! $document->__isInitialized()) {
1578
            $document->__load();
1579
        }
1580
1581 559
        return $this->reflFields[$field]->getValue($document);
1582
    }
1583 559
1584 559
    /**
1585
     * Gets the mapping of a field.
1586 424
     *
1587 559
     * @param string $fieldName The field name.
1588
     *
1589
     * @return array  The field mapping.
1590
     *
1591
     * @throws MappingException If the $fieldName is not found in the fieldMappings array.
1592
     */
1593
    public function getFieldMapping($fieldName)
1594
    {
1595
        if (! isset($this->fieldMappings[$fieldName])) {
1596
            throw MappingException::mappingNotFound($this->name, $fieldName);
1597
        }
1598
        return $this->fieldMappings[$fieldName];
1599
    }
1600 4
1601
    /**
1602 4
     * Gets mappings of fields holding embedded document(s).
1603 4
     *
1604 4
     * @return array of field mappings
1605
     */
1606
    public function getEmbeddedFieldsMappings()
1607
    {
1608
        return array_filter(
1609
            $this->associationMappings,
1610
            function ($assoc) {
1611
                return ! empty($assoc['embedded']);
1612
            }
1613
        );
1614
    }
1615
1616
    /**
1617
     * Gets the field mapping by its DB name.
1618 1
     * E.g. it returns identifier's mapping when called with _id.
1619
     *
1620 1
     * @param string $dbFieldName
1621 1
     *
1622 1
     * @return array
1623
     * @throws MappingException
1624
     */
1625
    public function getFieldMappingByDbFieldName($dbFieldName)
1626
    {
1627
        foreach ($this->fieldMappings as $mapping) {
1628
            if ($mapping['name'] === $dbFieldName) {
1629
                return $mapping;
1630
            }
1631
        }
1632 493
1633
        throw MappingException::mappingNotFoundByDbName($this->name, $dbFieldName);
1634 493
    }
1635
1636
    /**
1637
     * Check if the field is not null.
1638
     *
1639
     * @param string $fieldName The field name
1640
     *
1641
     * @return bool  TRUE if the field is not null, FALSE otherwise.
1642 886
     */
1643
    public function isNullable($fieldName)
1644 886
    {
1645 886
        $mapping = $this->getFieldMapping($fieldName);
1646
        if ($mapping !== false) {
1647
            return isset($mapping['nullable']) && $mapping['nullable'] === true;
1648
        }
1649
        return false;
1650
    }
1651
1652
    /**
1653
     * Checks whether the document has a discriminator field and value configured.
1654
     *
1655
     * @return bool
1656
     */
1657
    public function hasDiscriminator()
1658
    {
1659
        return isset($this->discriminatorField, $this->discriminatorValue);
1660 566
    }
1661
1662 566
    /**
1663
     * Sets the type of Id generator to use for the mapped class.
1664
     *
1665
     * @param string $generatorType Generator type.
1666
     */
1667
    public function setIdGeneratorType($generatorType)
1668
    {
1669
        $this->generatorType = $generatorType;
1670 885
    }
1671
1672 885
    /**
1673
     * Sets the Id generator options.
1674
     *
1675
     * @param array $generatorOptions Generator options.
1676
     */
1677
    public function setIdGeneratorOptions($generatorOptions)
1678
    {
1679
        $this->generatorOptions = $generatorOptions;
1680
    }
1681
1682
    /**
1683
     * @return bool
1684
     */
1685
    public function isInheritanceTypeNone()
1686
    {
1687
        return $this->inheritanceType === self::INHERITANCE_TYPE_NONE;
1688
    }
1689
1690 2
    /**
1691
     * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.
1692 2
     *
1693 2
     * @return bool
1694 1
     */
1695
    public function isInheritanceTypeSingleCollection()
1696 2
    {
1697
        return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION;
1698
    }
1699 2
1700
    /**
1701
     * Checks whether the mapped class uses the COLLECTION_PER_CLASS inheritance mapping strategy.
1702
     *
1703
     * @return bool
1704
     */
1705
    public function isInheritanceTypeCollectionPerClass()
1706
    {
1707
        return $this->inheritanceType === self::INHERITANCE_TYPE_COLLECTION_PER_CLASS;
1708 1362
    }
1709
1710 1362
    /**
1711
     * Sets the mapped subclasses of this class.
1712 1362
     *
1713 1361
     * @param string[] $subclasses The names of all mapped subclasses.
1714
     */
1715
    public function setSubclasses(array $subclasses)
1716 100
    {
1717 100
        foreach ($subclasses as $subclass) {
1718
            if (strpos($subclass, '\\') === false && strlen($this->namespace)) {
1719
                $this->subClasses[] = $this->namespace . '\\' . $subclass;
1720
            } else {
1721
                $this->subClasses[] = $subclass;
1722
            }
1723
        }
1724
    }
1725
1726
    /**
1727
     * Sets the parent class names.
1728
     * Assumes that the class names in the passed array are in the order:
1729
     * directParent -> directParentParent -> directParentParentParent ... -> root.
1730
     *
1731
     * @param string[] $classNames
1732
     */
1733
    public function setParentClasses(array $classNames)
1734
    {
1735
        $this->parentClasses = $classNames;
1736
1737
        if (count($classNames) <= 0) {
1738
            return;
1739
        }
1740
1741
        $this->rootDocumentName = array_pop($classNames);
1742
    }
1743
1744
    /**
1745
     * Checks whether the class will generate a new \MongoDB\BSON\ObjectId instance for us.
1746
     *
1747
     * @return bool TRUE if the class uses the AUTO generator, FALSE otherwise.
1748
     */
1749
    public function isIdGeneratorAuto()
1750
    {
1751
        return $this->generatorType === self::GENERATOR_TYPE_AUTO;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->generatorType (string) and self::GENERATOR_TYPE_AUTO (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1752
    }
1753
1754
    /**
1755
     * Checks whether the class will use a collection to generate incremented identifiers.
1756
     *
1757
     * @return bool TRUE if the class uses the INCREMENT generator, FALSE otherwise.
1758
     */
1759
    public function isIdGeneratorIncrement()
1760
    {
1761
        return $this->generatorType === self::GENERATOR_TYPE_INCREMENT;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->generatorType (string) and self::GENERATOR_TYPE_INCREMENT (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1762
    }
1763
1764
    /**
1765
     * Checks whether the class will generate a uuid id.
1766
     *
1767 67
     * @return bool TRUE if the class uses the UUID generator, FALSE otherwise.
1768
     */
1769 67
    public function isIdGeneratorUuid()
1770 1
    {
1771
        return $this->generatorType === self::GENERATOR_TYPE_UUID;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->generatorType (string) and self::GENERATOR_TYPE_UUID (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1772
    }
1773 66
1774 66
    /**
1775 66
     * Checks whether the class uses no id generator.
1776
     *
1777
     * @return bool TRUE if the class does not use any id generator, FALSE otherwise.
1778
     */
1779
    public function isIdGeneratorNone()
1780
    {
1781
        return $this->generatorType === self::GENERATOR_TYPE_NONE;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->generatorType (string) and self::GENERATOR_TYPE_NONE (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1782 886
    }
1783
1784 886
    /**
1785 886
     * Sets the version field mapping used for versioning. Sets the default
1786
     * value to use depending on the column type.
1787
     *
1788
     * @param array $mapping The version field mapping array
1789
     *
1790
     * @throws LockException
1791
     */
1792
    public function setVersionMapping(array &$mapping)
1793 886
    {
1794
        if ($mapping['type'] !== 'int' && $mapping['type'] !== 'date') {
1795 886
            throw LockException::invalidVersionFieldType($mapping['type']);
1796 886
        }
1797
1798
        $this->isVersioned  = true;
1799
        $this->versionField = $mapping['fieldName'];
1800
    }
1801
1802
    /**
1803
     * Sets whether this class is to be versioned for optimistic locking.
1804
     *
1805
     * @param bool $bool
1806 22
     */
1807
    public function setVersioned($bool)
1808 22
    {
1809 1
        $this->isVersioned = $bool;
1810
    }
1811
1812 21
    /**
1813 21
     * Sets the name of the field that is to be used for versioning if this class is
1814 21
     * versioned for optimistic locking.
1815
     *
1816
     * @param string $versionField
1817
     */
1818
    public function setVersionField($versionField)
1819
    {
1820
        $this->versionField = $versionField;
1821
    }
1822
1823
    /**
1824
     * Sets the version field mapping used for versioning. Sets the default
1825
     * value to use depending on the column type.
1826
     *
1827
     * @param array $mapping The version field mapping array
1828
     *
1829
     * @throws LockException
1830
     */
1831
    public function setLockMapping(array &$mapping)
1832
    {
1833
        if ($mapping['type'] !== 'int') {
1834
            throw LockException::invalidLockFieldType($mapping['type']);
1835
        }
1836
1837
        $this->isLockable = true;
1838
        $this->lockField = $mapping['fieldName'];
1839
    }
1840 5
1841
    /**
1842 5
     * Sets whether this class is to allow pessimistic locking.
1843 5
     *
1844
     * @param bool $bool
1845
     */
1846
    public function setLockable($bool)
1847
    {
1848
        $this->isLockable = $bool;
1849
    }
1850
1851
    /**
1852
     * Sets the name of the field that is to be used for storing whether a document
1853
     * is currently locked or not.
1854
     *
1855
     * @param string $lockField
1856
     */
1857
    public function setLockField($lockField)
1858
    {
1859
        $this->lockField = $lockField;
1860
    }
1861
1862
    /**
1863
     * Marks this class as read only, no change tracking is applied to it.
1864 23
     */
1865
    public function markReadOnly()
1866 23
    {
1867 23
        $this->isReadOnly = true;
1868
    }
1869
1870
    /**
1871
     * {@inheritDoc}
1872
     */
1873 4
    public function getFieldNames()
1874
    {
1875 4
        return array_keys($this->fieldMappings);
1876 2
    }
1877
1878
    /**
1879 2
     * {@inheritDoc}
1880
     */
1881
    public function getAssociationNames()
1882
    {
1883
        return array_keys($this->associationMappings);
1884
    }
1885
1886
    /**
1887 1
     * {@inheritDoc}
1888
     */
1889 1
    public function getTypeOfField($fieldName)
1890
    {
1891
        return isset($this->fieldMappings[$fieldName]) ?
1892
            $this->fieldMappings[$fieldName]['type'] : null;
1893 1
    }
1894
1895
    /**
1896
     * {@inheritDoc}
1897 1
     */
1898
    public function getAssociationTargetClass($assocName)
1899
    {
1900
        if (! isset($this->associationMappings[$assocName])) {
1901
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1902
        }
1903
1904
        return $this->associationMappings[$assocName]['targetDocument'];
1905
    }
1906
1907
    /**
1908
     * Retrieve the collectionClass associated with an association
1909
     *
1910
     * @param string $assocName
1911
     */
1912
    public function getAssociationCollectionClass($assocName)
1913
    {
1914
        if (! isset($this->associationMappings[$assocName])) {
1915
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1916
        }
1917
1918
        if (! array_key_exists('collectionClass', $this->associationMappings[$assocName])) {
1919
            throw new InvalidArgumentException("collectionClass can only be applied to 'embedMany' and 'referenceMany' associations.");
1920
        }
1921
1922
        return $this->associationMappings[$assocName]['collectionClass'];
1923
    }
1924
1925 1403
    /**
1926
     * {@inheritDoc}
1927 1403
     */
1928 8
    public function isAssociationInverseSide($fieldName)
1929
    {
1930 1403
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1931
    }
1932
1933 1403
    /**
1934 1394
     * {@inheritDoc}
1935
     */
1936 1403
    public function getAssociationMappedByTargetField($fieldName)
1937 1
    {
1938
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1939 1402
    }
1940 1
1941
    /**
1942 1401
     * Map a field.
1943 1103
     *
1944
     * @param array $mapping The mapping information.
1945 1401
     *
1946 53
     * @return array
1947 51
     *
1948
     * @throws MappingException
1949 53
     */
1950
    public function mapField(array $mapping)
1951 1401
    {
1952 53
        if (! isset($mapping['fieldName']) && isset($mapping['name'])) {
1953 53
            $mapping['fieldName'] = $mapping['name'];
1954 1
        }
1955
        if (! isset($mapping['fieldName'])) {
1956
            throw MappingException::missingFieldName($this->name);
1957
        }
1958 1400
        if (! isset($mapping['name'])) {
1959 114
            $mapping['name'] = $mapping['fieldName'];
1960 114
        }
1961 94
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1962
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, $mapping['name']);
1963
        }
1964 63
        if ($this->discriminatorField !== null && $this->discriminatorField === $mapping['name']) {
1965
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1966
        }
1967
        if (isset($mapping['targetDocument']) && strpos($mapping['targetDocument'], '\\') === false && strlen($this->namespace)) {
1968 1400
            $mapping['targetDocument'] = $this->namespace . '\\' . $mapping['targetDocument'];
1969 1
        }
1970
        if (isset($mapping['collectionClass'])) {
1971
            if (strpos($mapping['collectionClass'], '\\') === false && strlen($this->namespace)) {
1972 1399
                $mapping['collectionClass'] = $this->namespace . '\\' . $mapping['collectionClass'];
1973
            }
1974 1399
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1975 1105
        }
1976
        if (! empty($mapping['collectionClass'])) {
1977
            $rColl = new \ReflectionClass($mapping['collectionClass']);
1978 1399
            if (! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1979 1065
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1980 1394
            }
1981 903
        }
1982
1983
        if (isset($mapping['discriminatorMap'])) {
1984 1399
            foreach ($mapping['discriminatorMap'] as $key => $class) {
1985 1399
                if (strpos($class, '\\') !== false || ! strlen($this->namespace)) {
1986 1399
                    continue;
1987 1399
                }
1988 1399
1989
                $mapping['discriminatorMap'][$key] = $this->namespace . '\\' . $class;
1990 1399
            }
1991 1368
        }
1992 1368
1993 1368
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1994 1362
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1995
        }
1996 1368
1997 1368
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : [];
1998 1368
1999 1296
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
2000 1296
            $cascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
2001
        }
2002 145
2003 56
        if (isset($mapping['embedded'])) {
2004 89
            unset($mapping['cascade']);
2005 77
        } elseif (isset($mapping['cascade'])) {
2006
            $mapping['cascade'] = $cascades;
2007
        }
2008 1368
2009
        $mapping['isCascadeRemove'] = in_array('remove', $cascades);
2010
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
2011 1399
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
2012 38
        $mapping['isCascadeMerge'] = in_array('merge', $cascades);
2013
        $mapping['isCascadeDetach'] = in_array('detach', $cascades);
2014
2015 1399
        if (isset($mapping['id']) && $mapping['id'] === true) {
2016 1399
            $mapping['name'] = '_id';
2017 1399
            $this->identifier = $mapping['fieldName'];
2018 1399
            if (isset($mapping['strategy'])) {
2019
                $this->generatorType = constant(self::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
2020 3
            }
2021
            $this->generatorOptions = $mapping['options'] ?? [];
2022
            switch ($this->generatorType) {
2023 1396
                case self::GENERATOR_TYPE_AUTO:
2024 1396
                    $mapping['type'] = 'id';
2025 4
                    break;
2026
                default:
2027
                    if (! empty($this->generatorOptions['type'])) {
2028 1392
                        $mapping['type'] = $this->generatorOptions['type'];
2029 1
                    } elseif (empty($mapping['type'])) {
2030
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
2031
                    }
2032 1391
            }
2033 3
            unset($this->generatorOptions['type']);
2034
        }
2035
2036 1388
        if (! isset($mapping['nullable'])) {
2037 996
            $mapping['nullable'] = false;
2038
        }
2039 1388
2040 951
        if (isset($mapping['reference'])
2041
            && isset($mapping['storeAs'])
2042 1388
            && $mapping['storeAs'] === self::REFERENCE_STORE_AS_ID
2043 942
            && ! isset($mapping['targetDocument'])
2044
        ) {
2045 1388
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
2046 976
        }
2047
2048
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
2049 1388
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
2050 123
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
2051
        }
2052
2053
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
2054
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
2055
        }
2056
2057
        if (isset($mapping['repositoryMethod']) && ! (empty($mapping['skip']) && empty($mapping['limit']) && empty($mapping['sort']))) {
2058 1388
            throw MappingException::repositoryMethodCanNotBeCombinedWithSkipLimitAndSort($this->name, $mapping['fieldName']);
2059 67
        }
2060 67
2061
        if (isset($mapping['reference']) && $mapping['type'] === 'one') {
2062 1388
            $mapping['association'] = self::REFERENCE_ONE;
2063 22
        }
2064 22
        if (isset($mapping['reference']) && $mapping['type'] === 'many') {
2065
            $mapping['association'] = self::REFERENCE_MANY;
2066 1388
        }
2067 1388
        if (isset($mapping['embedded']) && $mapping['type'] === 'one') {
2068 1388
            $mapping['association'] = self::EMBED_ONE;
2069 1061
        }
2070 84
        if (isset($mapping['embedded']) && $mapping['type'] === 'many') {
2071 84
            $mapping['association'] = self::EMBED_MANY;
2072
        }
2073 1061
2074 813
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
2075 813
            $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
2076
        }
2077 1061
2078 58
        if (isset($mapping['version'])) {
2079 58
            $mapping['notSaved'] = true;
2080
            $this->setVersionMapping($mapping);
2081 1061
        }
2082 1043
        if (isset($mapping['lock'])) {
2083
            $mapping['notSaved'] = true;
2084
            $this->setLockMapping($mapping);
2085
        }
2086 1388
        $mapping['isOwningSide'] = true;
2087
        $mapping['isInverseSide'] = false;
2088
        if (isset($mapping['reference'])) {
2089
            if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
2090 1388
                $mapping['isOwningSide'] = true;
2091
                $mapping['isInverseSide'] = false;
2092 1387
            }
2093 1387
            if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
2094 1195
                $mapping['isInverseSide'] = true;
2095
                $mapping['isOwningSide'] = false;
2096
            }
2097 1387
            if (isset($mapping['repositoryMethod'])) {
2098 1386
                $mapping['isInverseSide'] = true;
2099 1386
                $mapping['isOwningSide'] = false;
2100
            }
2101 1386
            if (! isset($mapping['orphanRemoval'])) {
2102
                $mapping['orphanRemoval'] = false;
2103
            }
2104
        }
2105
2106
        if (! empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || ! $mapping['isInverseSide'])) {
2107
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
2108
        }
2109
2110
        if ($this->isFile && ! $this->isAllowedGridFSField($mapping['name'])) {
2111
            throw MappingException::fieldNotAllowedForGridFS($this->name, $mapping['fieldName']);
2112
        }
2113
2114
        $this->applyStorageStrategy($mapping);
2115
2116
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
2117 6
        if (isset($mapping['association'])) {
2118
            $this->associationMappings[$mapping['fieldName']] = $mapping;
2119
        }
2120
2121 6
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
2122
        $reflProp->setAccessible(true);
2123
        $this->reflFields[$mapping['fieldName']] = $reflProp;
2124
2125
        return $mapping;
2126
    }
2127
2128
    /**
2129
     * Determines which fields get serialized.
2130
     *
2131
     * It is only serialized what is necessary for best unserialization performance.
2132
     * That means any metadata properties that are not set or empty or simply have
2133
     * their default value are NOT serialized.
2134
     *
2135
     * Parts that are also NOT serialized because they can not be properly unserialized:
2136
     *      - reflClass (ReflectionClass)
2137
     *      - reflFields (ReflectionProperty array)
2138
     *
2139
     * @return array The names of all the fields that should be serialized.
2140 6
     */
2141
    public function __sleep()
2142
    {
2143
        // This metadata is always serialized/cached.
2144 6
        $serialized = [
2145 1
            'fieldMappings',
2146
            'associationMappings',
2147
            'identifier',
2148 6
            'name',
2149 4
            'namespace', // TODO: REMOVE
2150 4
            'db',
2151 4
            'collection',
2152 4
            'readPreference',
2153 4
            'readPreferenceTags',
2154 4
            'writeConcern',
2155 4
            'rootDocumentName',
2156
            'generatorType',
2157
            'generatorOptions',
2158 6
            'idGenerator',
2159 1
            'indexes',
2160
            'shardKey',
2161
        ];
2162 6
2163 1
        // The rest of the metadata is only serialized if necessary.
2164
        if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {
2165
            $serialized[] = 'changeTrackingPolicy';
2166 6
        }
2167
2168
        if ($this->customRepositoryClassName) {
2169
            $serialized[] = 'customRepositoryClassName';
2170 6
        }
2171
2172
        if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE || $this->discriminatorField !== null) {
2173
            $serialized[] = 'inheritanceType';
2174
            $serialized[] = 'discriminatorField';
2175 6
            $serialized[] = 'discriminatorValue';
2176
            $serialized[] = 'discriminatorMap';
2177
            $serialized[] = 'defaultDiscriminatorValue';
2178
            $serialized[] = 'parentClasses';
2179 6
            $serialized[] = 'subClasses';
2180 1
        }
2181 1
2182 1
        if ($this->isMappedSuperclass) {
2183
            $serialized[] = 'isMappedSuperclass';
2184
        }
2185 6
2186
        if ($this->isEmbeddedDocument) {
2187
            $serialized[] = 'isEmbeddedDocument';
2188
        }
2189 6
2190
        if ($this->isQueryResultDocument) {
2191
            $serialized[] = 'isQueryResultDocument';
2192
        }
2193
2194
        if ($this->isFile) {
2195
            $serialized[] = 'isFile';
2196 6
        }
2197
2198
        if ($this->isVersioned) {
2199 6
            $serialized[] = 'isVersioned';
2200 6
            $serialized[] = 'versionField';
2201
        }
2202 6
2203 3
        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...
2204 1
            $serialized[] = 'lifecycleCallbacks';
2205
        }
2206 3
2207
        if ($this->collectionCapped) {
2208 3
            $serialized[] = 'collectionCapped';
2209 3
            $serialized[] = 'collectionSize';
2210
            $serialized[] = 'collectionMax';
2211 6
        }
2212
2213
        if ($this->isReadOnly) {
2214
            $serialized[] = 'isReadOnly';
2215
        }
2216
2217
        return $serialized;
2218 355
    }
2219
2220 355
    /**
2221
     * Restores some state that can not be serialized/unserialized.
2222
     *
2223
     */
2224
    public function __wakeup()
2225
    {
2226
        // Restore ReflectionClass and properties
2227
        $this->reflClass = new \ReflectionClass($this->name);
2228
        $this->instantiator = $this->instantiator ?: new Instantiator();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->instantiator ?: n...antiator\Instantiator() can also be of type object<Doctrine\Instantiator\Instantiator>. However, the property $instantiator is declared as type object<Doctrine\Instanti...antiatorInterface>|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2229
2230
        foreach ($this->fieldMappings as $field => $mapping) {
2231
            if (isset($mapping['declared'])) {
2232
                $reflField = new \ReflectionProperty($mapping['declared'], $field);
2233
            } else {
2234
                $reflField = $this->reflClass->getProperty($field);
2235
            }
2236
            $reflField->setAccessible(true);
2237
            $this->reflFields[$field] = $reflField;
2238
        }
2239
    }
2240
2241
    /**
2242
     * Creates a new instance of the mapped class, without invoking the constructor.
2243
     *
2244
     * @return object
2245
     */
2246
    public function newInstance()
2247
    {
2248
        return $this->instantiator->instantiate($this->name);
2249
    }
2250
2251
    private function isAllowedGridFSField(string $name): bool
2252
    {
2253
        return in_array($name, ['_id', 'chunkSize', 'filename', 'length', 'metadata', 'uploadDate'], true);
2254
    }
2255
}
2256