Completed
Pull Request — master (#1790)
by Andreas
20:58
created

ClassMetadata::isIdentifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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