Completed
Pull Request — master (#1790)
by Andreas
15:40 queued 17s
created

ClassMetadata::setDiscriminatorMap()   C

Complexity

Conditions 8
Paths 10

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 8.1867

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 12
cts 14
cp 0.8571
rs 6.1403
c 0
b 0
f 0
cc 8
eloc 14
nc 10
nop 1
crap 8.1867
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
     * READ_ONLY: A flag for whether or not this document is read-only.
482
     *
483
     * @var bool
484
     */
485
    public $isReadOnly;
486
487
    /** @var InstantiatorInterface|null */
488
    private $instantiator;
489
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 1496
    public function __construct($documentName)
497
    {
498 1496
        $this->name = $documentName;
499 1496
        $this->rootDocumentName = $documentName;
500 1496
        $this->reflClass = new \ReflectionClass($documentName);
501 1496
        $this->namespace = $this->reflClass->getNamespaceName();
502 1496
        $this->setCollection($this->reflClass->getShortName());
503 1496
        $this->instantiator = new Instantiator();
504 1496
    }
505
506
    /**
507
     * Helper method to get reference id of ref* type references
508
     * @param mixed  $reference
509
     * @param string $storeAs
510
     * @return mixed
511
     * @internal
512
     */
513 122
    public static function getReferenceId($reference, $storeAs)
514
    {
515 122
        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 187
    private static function getReferencePrefix($storeAs)
524
    {
525 187
        if (! in_array($storeAs, [self::REFERENCE_STORE_AS_REF, self::REFERENCE_STORE_AS_DB_REF, self::REFERENCE_STORE_AS_DB_REF_WITH_DB])) {
526
            throw new \LogicException('Can only get a reference prefix for DBRef and reference arrays');
527
        }
528
529 187
        return $storeAs === self::REFERENCE_STORE_AS_REF ? '' : '$';
530
    }
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
     * @return string
537
     * @internal
538
     */
539 134
    public static function getReferenceFieldName($storeAs, $pathPrefix = '')
540
    {
541 134
        if ($storeAs === self::REFERENCE_STORE_AS_ID) {
542 94
            return $pathPrefix;
543
        }
544
545 122
        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
    /**
549
     * {@inheritDoc}
550
     */
551 1382
    public function getReflectionClass()
552
    {
553 1382
        if (! $this->reflClass) {
554
            $this->reflClass = new \ReflectionClass($this->name);
555
        }
556
557 1382
        return $this->reflClass;
558
    }
559
560
    /**
561
     * {@inheritDoc}
562
     */
563 327
    public function isIdentifier($fieldName)
564
    {
565 327
        return $this->identifier === $fieldName;
566
    }
567
568
    /**
569
     * INTERNAL:
570
     * Sets the mapped identifier field of this class.
571
     *
572
     * @param string $identifier
573
     */
574 893
    public function setIdentifier($identifier)
575
    {
576 893
        $this->identifier = $identifier;
577 893
    }
578
579
    /**
580
     * {@inheritDoc}
581
     *
582
     * Since MongoDB only allows exactly one identifier field
583
     * this will always return an array with only one value
584
     */
585 39
    public function getIdentifier()
586
    {
587 39
        return [$this->identifier];
588
    }
589
590
    /**
591
     * {@inheritDoc}
592
     *
593
     * Since MongoDB only allows exactly one identifier field
594
     * this will always return an array with only one value
595
     */
596 103
    public function getIdentifierFieldNames()
597
    {
598 103
        return [$this->identifier];
599
    }
600
601
    /**
602
     * {@inheritDoc}
603
     */
604 897
    public function hasField($fieldName)
605
    {
606 897
        return isset($this->fieldMappings[$fieldName]);
607
    }
608
609
    /**
610
     * Sets the inheritance type used by the class and it's subclasses.
611
     *
612
     * @param int $type
613
     */
614 909
    public function setInheritanceType($type)
615
    {
616 909
        $this->inheritanceType = $type;
617 909
    }
618
619
    /**
620
     * Checks whether a mapped field is inherited from an entity superclass.
621
     *
622
     * @param  string $fieldName
623
     *
624
     * @return bool TRUE if the field is inherited, FALSE otherwise.
625
     */
626 1378
    public function isInheritedField($fieldName)
627
    {
628 1378
        return isset($this->fieldMappings[$fieldName]['inherited']);
629
    }
630
631
    /**
632
     * Registers a custom repository class for the document class.
633
     *
634
     * @param string $repositoryClassName The class name of the custom repository.
635
     */
636 841
    public function setCustomRepositoryClass($repositoryClassName)
637
    {
638 841
        if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
639
            return;
640
        }
641
642 841
        if ($repositoryClassName && strpos($repositoryClassName, '\\') === false && strlen($this->namespace)) {
643 3
            $repositoryClassName = $this->namespace . '\\' . $repositoryClassName;
644
        }
645
646 841
        $this->customRepositoryClassName = $repositoryClassName;
647 841
    }
648
649
    /**
650
     * Dispatches the lifecycle event of the given document by invoking all
651
     * registered callbacks.
652
     *
653
     * @param string $event     Lifecycle event
654
     * @param object $document  Document on which the event occurred
655
     * @param array  $arguments Arguments to pass to all callbacks
656
     * @throws \InvalidArgumentException If document class is not this class or
657
     *                                   a Proxy of this class.
658
     */
659 605
    public function invokeLifecycleCallbacks($event, $document, ?array $arguments = null)
660
    {
661 605
        if (! $document instanceof $this->name) {
662 1
            throw new \InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"', $this->name, get_class($document)));
663
        }
664
665 604
        if (empty($this->lifecycleCallbacks[$event])) {
666 589
            return;
667
        }
668
669 179
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
670 179
            if ($arguments !== null) {
671 178
                call_user_func_array([$document, $callback], $arguments);
672
            } else {
673 179
                $document->$callback();
674
            }
675
        }
676 179
    }
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
     * @return array
695
     */
696
    public function getLifecycleCallbacks($event)
697
    {
698
        return $this->lifecycleCallbacks[$event] ?? [];
699
    }
700
701
    /**
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 808
    public function addLifecycleCallback($callback, $event)
710
    {
711 808
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
712 1
            return;
713
        }
714
715 808
        $this->lifecycleCallbacks[$event][] = $callback;
716 808
    }
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
     */
725 892
    public function setLifecycleCallbacks(array $callbacks)
726
    {
727 892
        $this->lifecycleCallbacks = $callbacks;
728 892
    }
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
     * @param string       $method Method name
737
     * @param array|string $fields Database field name(s)
738
     */
739 14
    public function registerAlsoLoadMethod($method, $fields)
740
    {
741 14
        $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : [$fields];
742 14
    }
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 892
    public function setAlsoLoadMethods(array $methods)
752
    {
753 892
        $this->alsoLoadMethods = $methods;
754 892
    }
755
756
    /**
757
     * Sets the discriminator field.
758
     *
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
     *
763
     * @param string $discriminatorField
764
     *
765
     * @throws MappingException If the discriminator field conflicts with the
766
     *                          "name" attribute of a mapped field.
767
     */
768 918
    public function setDiscriminatorField($discriminatorField)
769
    {
770 918
        if ($this->isFile) {
771
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
772
        }
773
774 918
        if ($discriminatorField === null) {
775 850
            $this->discriminatorField = null;
776
777 850
            return;
778
        }
779
780
        // Handle array argument with name/fieldName keys for BC
781 120
        if (is_array($discriminatorField)) {
782
            if (isset($discriminatorField['name'])) {
783
                $discriminatorField = $discriminatorField['name'];
784
            } elseif (isset($discriminatorField['fieldName'])) {
785
                $discriminatorField = $discriminatorField['fieldName'];
786
            }
787
        }
788
789 120
        foreach ($this->fieldMappings as $fieldMapping) {
790 4
            if ($discriminatorField === $fieldMapping['name']) {
791 4
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
792
            }
793
        }
794
795 119
        $this->discriminatorField = $discriminatorField;
796 119
    }
797
798
    /**
799
     * Sets the discriminator values used by this class.
800
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
801
     *
802
     * @param array $map
803
     *
804
     * @throws MappingException
805
     */
806 911
    public function setDiscriminatorMap(array $map)
807
    {
808 911
        if ($this->isFile) {
809
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
810
        }
811
812 911
        foreach ($map as $value => $className) {
813 115
            if (strpos($className, '\\') === false && strlen($this->namespace)) {
814 85
                $className = $this->namespace . '\\' . $className;
815
            }
816 115
            $this->discriminatorMap[$value] = $className;
817 115
            if ($this->name === $className) {
818 107
                $this->discriminatorValue = $value;
819
            } else {
820 114
                if (! class_exists($className)) {
821
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
822
                }
823 114
                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 115
                    $this->subClasses[] = $className;
825
                }
826
            }
827
        }
828 911
    }
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
     */
838 895
    public function setDefaultDiscriminatorValue($defaultDiscriminatorValue)
839
    {
840 895
        if ($this->isFile) {
841
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
842
        }
843
844 895
        if ($defaultDiscriminatorValue === null) {
845 892
            $this->defaultDiscriminatorValue = null;
846
847 892
            return;
848
        }
849
850 51
        if (! array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
851
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
852
        }
853
854 51
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
855 51
    }
856
857
    /**
858
     * Sets the discriminator value for this class.
859
     * Used for JOINED/SINGLE_TABLE inheritance and multiple document types in a single
860
     * collection.
861
     *
862
     * @param string $value
863
     *
864
     * @throws MappingException
865
     */
866 3
    public function setDiscriminatorValue($value)
867
    {
868 3
        if ($this->isFile) {
869
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
870
        }
871
872 3
        $this->discriminatorMap[$value] = $this->name;
873 3
        $this->discriminatorValue = $value;
874 3
    }
875
876
    /**
877
     * Add a index for this Document.
878
     *
879
     * @param array $keys    Array of keys for the index.
880
     * @param array $options Array of options for the index.
881
     */
882 186
    public function addIndex($keys, array $options = [])
883
    {
884 186
        $this->indexes[] = [
885
            'keys' => array_map(function ($value) {
886 186
                if ($value === 1 || $value === -1) {
887 48
                    return (int) $value;
888
                }
889 186
                if (is_string($value)) {
890 186
                    $lower = strtolower($value);
891 186
                    if ($lower === 'asc') {
892 179
                        return 1;
893
                    }
894
895 55
                    if ($lower === 'desc') {
896
                        return -1;
897
                    }
898
                }
899 55
                return $value;
900 186
            }, $keys),
901 186
            'options' => $options,
902
        ];
903 186
    }
904
905
    /**
906
     * Returns the array of indexes for this Document.
907
     *
908
     * @return array $indexes The array of indexes.
909
     */
910 24
    public function getIndexes()
911
    {
912 24
        return $this->indexes;
913
    }
914
915
    /**
916
     * Checks whether this document has indexes or not.
917
     *
918
     * @return bool
919
     */
920
    public function hasIndexes()
921
    {
922
        return $this->indexes ? true : false;
923
    }
924
925
    /**
926
     * Set shard key for this Document.
927
     *
928
     * @param array $keys    Array of document keys.
929
     * @param array $options Array of sharding options.
930
     *
931
     * @throws MappingException
932
     */
933 74
    public function setShardKey(array $keys, array $options = [])
934
    {
935 74
        if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && $this->shardKey !== null) {
936 2
            throw MappingException::shardKeyInSingleCollInheritanceSubclass($this->getName());
937
        }
938
939 74
        if ($this->isEmbeddedDocument) {
940 2
            throw MappingException::embeddedDocumentCantHaveShardKey($this->getName());
941
        }
942
943 72
        foreach (array_keys($keys) as $field) {
944 72
            if (! isset($this->fieldMappings[$field])) {
945 65
                continue;
946
            }
947
948 7
            if (in_array($this->fieldMappings[$field]['type'], ['many', 'collection'])) {
949 3
                throw MappingException::noMultiKeyShardKeys($this->getName(), $field);
950
            }
951
952 4
            if ($this->fieldMappings[$field]['strategy'] !== static::STORAGE_STRATEGY_SET) {
953 4
                throw MappingException::onlySetStrategyAllowedInShardKey($this->getName(), $field);
954
            }
955
        }
956
957 68
        $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
            'keys' => array_map(function ($value) {
959 68
                if ($value === 1 || $value === -1) {
960 5
                    return (int) $value;
961
                }
962 68
                if (is_string($value)) {
963 68
                    $lower = strtolower($value);
964 68
                    if ($lower === 'asc') {
965 66
                        return 1;
966
                    }
967
968 50
                    if ($lower === 'desc') {
969
                        return -1;
970
                    }
971
                }
972 50
                return $value;
973 68
            }, $keys),
974 68
            'options' => $options,
975
        ];
976 68
    }
977
978
    /**
979
     * @return array
980
     */
981 17
    public function getShardKey()
982
    {
983 17
        return $this->shardKey;
984
    }
985
986
    /**
987
     * Checks whether this document has shard key or not.
988
     *
989
     * @return bool
990
     */
991 1104
    public function isSharded()
992
    {
993 1104
        return $this->shardKey ? true : false;
994
    }
995
996
    /**
997
     * Sets the read preference used by this class.
998
     *
999
     * @param string     $readPreference
1000
     * @param array|null $tags
1001
     */
1002 892
    public function setReadPreference($readPreference, $tags)
1003
    {
1004 892
        $this->readPreference = $readPreference;
1005 892
        $this->readPreferenceTags = $tags;
1006 892
    }
1007
1008
    /**
1009
     * Sets the write concern used by this class.
1010
     *
1011
     * @param string $writeConcern
1012
     */
1013 902
    public function setWriteConcern($writeConcern)
1014
    {
1015 902
        $this->writeConcern = $writeConcern;
1016 902
    }
1017
1018
    /**
1019
     * @return string
1020
     */
1021 11
    public function getWriteConcern()
1022
    {
1023 11
        return $this->writeConcern;
1024
    }
1025
1026
    /**
1027
     * Whether there is a write concern configured for this class.
1028
     *
1029
     * @return bool
1030
     */
1031 552
    public function hasWriteConcern()
1032
    {
1033 552
        return $this->writeConcern !== null;
1034
    }
1035
1036
    /**
1037
     * Sets the change tracking policy used by this class.
1038
     *
1039
     * @param int $policy
1040
     */
1041 894
    public function setChangeTrackingPolicy($policy)
1042
    {
1043 894
        $this->changeTrackingPolicy = $policy;
1044 894
    }
1045
1046
    /**
1047
     * Whether the change tracking policy of this class is "deferred explicit".
1048
     *
1049
     * @return bool
1050
     */
1051 64
    public function isChangeTrackingDeferredExplicit()
1052
    {
1053 64
        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 574
    public function isChangeTrackingDeferredImplicit()
1062
    {
1063 574
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT;
1064
    }
1065
1066
    /**
1067
     * Whether the change tracking policy of this class is "notify".
1068
     *
1069
     * @return bool
1070
     */
1071 312
    public function isChangeTrackingNotify()
1072
    {
1073 312
        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 103
    public function getReflectionProperties()
1082
    {
1083 103
        return $this->reflFields;
1084
    }
1085
1086
    /**
1087
     * Gets a ReflectionProperty for a specific field of the mapped class.
1088
     *
1089
     * @param string $name
1090
     *
1091
     * @return \ReflectionProperty
1092
     */
1093
    public function getReflectionProperty($name)
1094
    {
1095
        return $this->reflFields[$name];
1096
    }
1097
1098
    /**
1099
     * {@inheritDoc}
1100
     */
1101 1386
    public function getName()
1102
    {
1103 1386
        return $this->name;
1104
    }
1105
1106
    /**
1107
     * The namespace this Document class belongs to.
1108
     *
1109
     * @return string $namespace The namespace name.
1110
     */
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
     */
1121 1308
    public function getDatabase()
1122
    {
1123 1308
        return $this->db;
1124
    }
1125
1126
    /**
1127
     * Set the database this Document is mapped to.
1128
     *
1129
     * @param string $db The database name
1130
     */
1131 95
    public function setDatabase($db)
1132
    {
1133 95
        $this->db = $db;
1134 95
    }
1135
1136
    /**
1137
     * Get the collection this Document is mapped to.
1138
     *
1139
     * @return string $collection The collection name.
1140
     */
1141 1303
    public function getCollection()
1142
    {
1143 1303
        return $this->collection;
1144
    }
1145
1146
    /**
1147
     * Sets the collection this Document is mapped to.
1148
     *
1149
     * @param array|string $name
1150
     *
1151
     * @throws \InvalidArgumentException
1152
     */
1153 1496
    public function setCollection($name)
1154
    {
1155 1496
        if (is_array($name)) {
1156 1
            if (! isset($name['name'])) {
1157
                throw new \InvalidArgumentException('A name key is required when passing an array to setCollection()');
1158
            }
1159 1
            $this->collectionCapped = $name['capped'] ?? false;
1160 1
            $this->collectionSize = $name['size'] ?? 0;
1161 1
            $this->collectionMax = $name['max'] ?? 0;
1162 1
            $this->collection = $name['name'];
1163
        } else {
1164 1496
            $this->collection = $name;
1165
        }
1166 1496
    }
1167
1168 15
    public function getBucketName(): ?string
1169
    {
1170 15
        return $this->bucketName;
1171
    }
1172
1173 57
    public function setBucketName(string $bucketName): void
1174
    {
1175 57
        $this->bucketName = $bucketName;
1176 57
        $this->setCollection($bucketName . '.files');
1177 57
    }
1178
1179
    /**
1180
     * Get whether or not the documents collection is capped.
1181
     *
1182
     * @return bool
1183
     */
1184 5
    public function getCollectionCapped()
1185
    {
1186 5
        return $this->collectionCapped;
1187
    }
1188
1189
    /**
1190
     * Set whether or not the documents collection is capped.
1191
     *
1192
     * @param bool $bool
1193
     */
1194 1
    public function setCollectionCapped($bool)
1195
    {
1196 1
        $this->collectionCapped = $bool;
1197 1
    }
1198
1199
    /**
1200
     * Get the collection size
1201
     *
1202
     * @return int
1203
     */
1204 5
    public function getCollectionSize()
1205
    {
1206 5
        return $this->collectionSize;
1207
    }
1208
1209
    /**
1210
     * Set the collection size.
1211
     *
1212
     * @param int $size
1213
     */
1214 1
    public function setCollectionSize($size)
1215
    {
1216 1
        $this->collectionSize = $size;
1217 1
    }
1218
1219
    /**
1220
     * Get the collection max.
1221
     *
1222
     * @return int
1223
     */
1224 5
    public function getCollectionMax()
1225
    {
1226 5
        return $this->collectionMax;
1227
    }
1228
1229
    /**
1230
     * Set the collection max.
1231
     *
1232
     * @param int $max
1233
     */
1234 1
    public function setCollectionMax($max)
1235
    {
1236 1
        $this->collectionMax = $max;
1237 1
    }
1238
1239
    /**
1240
     * Returns TRUE if this Document is mapped to a collection FALSE otherwise.
1241
     *
1242
     * @return bool
1243
     */
1244
    public function isMappedToCollection()
1245
    {
1246
        return $this->collection ? true : false;
1247
    }
1248
1249
    /**
1250
     * Validates the storage strategy of a mapping for consistency
1251
     * @param array $mapping
1252
     * @throws MappingException
1253
     */
1254 1405
    private function applyStorageStrategy(array &$mapping)
1255
    {
1256 1405
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1257 1387
            return;
1258
        }
1259
1260
        switch (true) {
1261 1370
            case $mapping['type'] === 'int':
1262 1369
            case $mapping['type'] === 'float':
1263 833
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1264 833
                $allowedStrategies = [self::STORAGE_STRATEGY_SET, self::STORAGE_STRATEGY_INCREMENT];
1265 833
                break;
1266
1267 1369
            case $mapping['type'] === 'many':
1268 1085
                $defaultStrategy = CollectionHelper::DEFAULT_STRATEGY;
1269
                $allowedStrategies = [
1270 1085
                    self::STORAGE_STRATEGY_PUSH_ALL,
1271 1085
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1272 1085
                    self::STORAGE_STRATEGY_SET,
1273 1085
                    self::STORAGE_STRATEGY_SET_ARRAY,
1274 1085
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1275 1085
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1276
                ];
1277 1085
                break;
1278
1279
            default:
1280 1356
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1281 1356
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1282
        }
1283
1284 1370
        if (! isset($mapping['strategy'])) {
1285 1361
            $mapping['strategy'] = $defaultStrategy;
1286
        }
1287
1288 1370
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1289
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1290
        }
1291
1292 1370
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1293 1370
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1294 1
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1295
        }
1296 1369
    }
1297
1298
    /**
1299
     * Map a single embedded document.
1300
     *
1301
     * @param array $mapping The mapping information.
1302
     */
1303 6
    public function mapOneEmbedded(array $mapping)
1304
    {
1305 6
        $mapping['embedded'] = true;
1306 6
        $mapping['type'] = 'one';
1307 6
        $this->mapField($mapping);
1308 5
    }
1309
1310
    /**
1311
     * Map a collection of embedded documents.
1312
     *
1313
     * @param array $mapping The mapping information.
1314
     */
1315 5
    public function mapManyEmbedded(array $mapping)
1316
    {
1317 5
        $mapping['embedded'] = true;
1318 5
        $mapping['type'] = 'many';
1319 5
        $this->mapField($mapping);
1320 5
    }
1321
1322
    /**
1323
     * Map a single document reference.
1324
     *
1325
     * @param array $mapping The mapping information.
1326
     */
1327 2
    public function mapOneReference(array $mapping)
1328
    {
1329 2
        $mapping['reference'] = true;
1330 2
        $mapping['type'] = 'one';
1331 2
        $this->mapField($mapping);
1332 2
    }
1333
1334
    /**
1335
     * Map a collection of document references.
1336
     *
1337
     * @param array $mapping The mapping information.
1338
     */
1339 1
    public function mapManyReference(array $mapping)
1340
    {
1341 1
        $mapping['reference'] = true;
1342 1
        $mapping['type'] = 'many';
1343 1
        $this->mapField($mapping);
1344 1
    }
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
     */
1353 119
    public function addInheritedFieldMapping(array $fieldMapping)
1354
    {
1355 119
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1356
1357 119
        if (! isset($fieldMapping['association'])) {
1358 119
            return;
1359
        }
1360
1361 70
        $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1362 70
    }
1363
1364
    /**
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 71
    public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
1375
    {
1376 71
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1377 71
    }
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 32
    public function hasReference($fieldName)
1386
    {
1387 32
        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 5
    public function hasEmbed($fieldName)
1397
    {
1398 5
        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 7
    public function hasAssociation($fieldName)
1407
    {
1408 7
        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
    /**
1447
     * Checks whether the class has a mapped association for the specified field
1448
     * and if yes, checks whether it is a collection-valued association (to-many).
1449
     *
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
    }
1458
1459
    /**
1460
     * 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
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_ONE;
1470
    }
1471
1472
    /**
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
    }
1484
1485
    /**
1486
     * Sets the ID generator used to generate IDs for instances of this class.
1487
     *
1488
     * @param AbstractIdGenerator $generator
1489
     */
1490 1322
    public function setIdGenerator($generator)
1491
    {
1492 1322
        $this->idGenerator = $generator;
1493 1322
    }
1494
1495
    /**
1496
     * Casts the identifier to its portable PHP type.
1497
     *
1498
     * @param mixed $id
1499
     * @return mixed $id
1500
     */
1501 601
    public function getPHPIdentifierValue($id)
1502
    {
1503 601
        $idType = $this->fieldMappings[$this->identifier]['type'];
1504 601
        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 664
    public function getDatabaseIdentifierValue($id)
1514
    {
1515 664
        $idType = $this->fieldMappings[$this->identifier]['type'];
1516 664
        return Type::getType($idType)->convertToDatabaseValue($id);
1517
    }
1518
1519
    /**
1520
     * Sets the document identifier of a document.
1521
     *
1522
     * The value will be converted to a PHP type before being set.
1523
     *
1524
     * @param object $document
1525
     * @param mixed  $id
1526
     */
1527 525
    public function setIdentifierValue($document, $id)
1528
    {
1529 525
        $id = $this->getPHPIdentifierValue($id);
1530 525
        $this->reflFields[$this->identifier]->setValue($document, $id);
1531 525
    }
1532
1533
    /**
1534
     * Gets the document identifier as a PHP type.
1535
     *
1536
     * @param object $document
1537
     * @return mixed $id
1538
     */
1539 608
    public function getIdentifierValue($document)
1540
    {
1541 608
        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
     */
1551
    public function getIdentifierValues($object)
1552
    {
1553
        return [$this->identifier => $this->getIdentifierValue($object)];
1554
    }
1555
1556
    /**
1557
     * Get the document identifier object as a database type.
1558
     *
1559
     * @param object $document
1560
     *
1561
     * @return ObjectId $id The ObjectId
1562
     */
1563 31
    public function getIdentifierObject($document)
1564
    {
1565 31
        return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));
1566
    }
1567
1568
    /**
1569
     * Sets the specified field to the specified value on the given document.
1570
     *
1571
     * @param object $document
1572
     * @param string $field
1573
     * @param mixed  $value
1574
     */
1575 8
    public function setFieldValue($document, $field, $value)
1576
    {
1577 8
        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 1
            $document->__load();
1581
        }
1582
1583 8
        $this->reflFields[$field]->setValue($document, $value);
1584 8
    }
1585
1586
    /**
1587
     * 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 27
    public function getFieldValue($document, $field)
1595
    {
1596 27
        if ($document instanceof Proxy && $field !== $this->identifier && ! $document->__isInitialized()) {
1597 1
            $document->__load();
1598
        }
1599
1600 27
        return $this->reflFields[$field]->getValue($document);
1601
    }
1602
1603
    /**
1604
     * 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 171
    public function getFieldMapping($fieldName)
1613
    {
1614 171
        if (! isset($this->fieldMappings[$fieldName])) {
1615 6
            throw MappingException::mappingNotFound($this->name, $fieldName);
1616
        }
1617 169
        return $this->fieldMappings[$fieldName];
1618
    }
1619
1620
    /**
1621
     * Gets mappings of fields holding embedded document(s).
1622
     *
1623
     * @return array of field mappings
1624
     */
1625 563
    public function getEmbeddedFieldsMappings()
1626
    {
1627 563
        return array_filter(
1628 563
            $this->associationMappings,
1629
            function ($assoc) {
1630 428
                return ! empty($assoc['embedded']);
1631 563
            }
1632
        );
1633
    }
1634
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
     * @throws MappingException
1643
     */
1644 6
    public function getFieldMappingByDbFieldName($dbFieldName)
1645
    {
1646 6
        foreach ($this->fieldMappings as $mapping) {
1647 6
            if ($mapping['name'] === $dbFieldName) {
1648 6
                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
     * @return bool  TRUE if the field is not null, FALSE otherwise.
1661
     */
1662 1
    public function isNullable($fieldName)
1663
    {
1664 1
        $mapping = $this->getFieldMapping($fieldName);
1665 1
        if ($mapping !== false) {
1666 1
            return isset($mapping['nullable']) && $mapping['nullable'] === true;
1667
        }
1668
        return false;
1669
    }
1670
1671
    /**
1672
     * Checks whether the document has a discriminator field and value configured.
1673
     *
1674
     * @return bool
1675
     */
1676 503
    public function hasDiscriminator()
1677
    {
1678 503
        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 892
    public function setIdGeneratorType($generatorType)
1687
    {
1688 892
        $this->generatorType = $generatorType;
1689 892
    }
1690
1691
    /**
1692
     * Sets the Id generator options.
1693
     *
1694
     * @param array $generatorOptions Generator options.
1695
     */
1696
    public function setIdGeneratorOptions($generatorOptions)
1697
    {
1698
        $this->generatorOptions = $generatorOptions;
1699
    }
1700
1701
    /**
1702
     * @return bool
1703
     */
1704 571
    public function isInheritanceTypeNone()
1705
    {
1706 571
        return $this->inheritanceType === self::INHERITANCE_TYPE_NONE;
1707
    }
1708
1709
    /**
1710
     * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.
1711
     *
1712
     * @return bool
1713
     */
1714 891
    public function isInheritanceTypeSingleCollection()
1715
    {
1716 891
        return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION;
1717
    }
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 2
    public function setSubclasses(array $subclasses)
1735
    {
1736 2
        foreach ($subclasses as $subclass) {
1737 2
            if (strpos($subclass, '\\') === false && strlen($this->namespace)) {
1738 1
                $this->subClasses[] = $this->namespace . '\\' . $subclass;
1739
            } else {
1740 2
                $this->subClasses[] = $subclass;
1741
            }
1742
        }
1743 2
    }
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 1377
    public function setParentClasses(array $classNames)
1753
    {
1754 1377
        $this->parentClasses = $classNames;
1755
1756 1377
        if (count($classNames) <= 0) {
1757 1376
            return;
1758
        }
1759
1760 103
        $this->rootDocumentName = array_pop($classNames);
1761 103
    }
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
     */
1768
    public function isIdGeneratorAuto()
1769
    {
1770
        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
    /**
1774
     * Checks whether the class will use a collection to generate incremented identifiers.
1775
     *
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
1783
    /**
1784
     * Checks whether the class will generate a uuid id.
1785
     *
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
    /**
1794
     * Checks whether the class uses no id generator.
1795
     *
1796
     * @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
     *
1807
     * @param array $mapping The version field mapping array
1808
     *
1809
     * @throws LockException
1810
     */
1811 70
    public function setVersionMapping(array &$mapping)
1812
    {
1813 70
        if ($mapping['type'] !== 'int' && $mapping['type'] !== 'date') {
1814 1
            throw LockException::invalidVersionFieldType($mapping['type']);
1815
        }
1816
1817 69
        $this->isVersioned  = true;
1818 69
        $this->versionField = $mapping['fieldName'];
1819 69
    }
1820
1821
    /**
1822
     * Sets whether this class is to be versioned for optimistic locking.
1823
     *
1824
     * @param bool $bool
1825
     */
1826 892
    public function setVersioned($bool)
1827
    {
1828 892
        $this->isVersioned = $bool;
1829 892
    }
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 892
    public function setVersionField($versionField)
1838
    {
1839 892
        $this->versionField = $versionField;
1840 892
    }
1841
1842
    /**
1843
     * 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 22
    public function setLockMapping(array &$mapping)
1851
    {
1852 22
        if ($mapping['type'] !== 'int') {
1853 1
            throw LockException::invalidLockFieldType($mapping['type']);
1854
        }
1855
1856 21
        $this->isLockable = true;
1857 21
        $this->lockField = $mapping['fieldName'];
1858 21
    }
1859
1860
    /**
1861
     * Sets whether this class is to allow pessimistic locking.
1862
     *
1863
     * @param bool $bool
1864
     */
1865
    public function setLockable($bool)
1866
    {
1867
        $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
     *
1874
     * @param string $lockField
1875
     */
1876
    public function setLockField($lockField)
1877
    {
1878
        $this->lockField = $lockField;
1879
    }
1880
1881
    /**
1882
     * Marks this class as read only, no change tracking is applied to it.
1883
     */
1884 5
    public function markReadOnly()
1885
    {
1886 5
        $this->isReadOnly = true;
1887 5
    }
1888
1889
    /**
1890
     * {@inheritDoc}
1891
     */
1892
    public function getFieldNames()
1893
    {
1894
        return array_keys($this->fieldMappings);
1895
    }
1896
1897
    /**
1898
     * {@inheritDoc}
1899
     */
1900
    public function getAssociationNames()
1901
    {
1902
        return array_keys($this->associationMappings);
1903
    }
1904
1905
    /**
1906
     * {@inheritDoc}
1907
     */
1908 23
    public function getTypeOfField($fieldName)
1909
    {
1910 23
        return isset($this->fieldMappings[$fieldName]) ?
1911 23
            $this->fieldMappings[$fieldName]['type'] : null;
1912
    }
1913
1914
    /**
1915
     * {@inheritDoc}
1916
     */
1917 4
    public function getAssociationTargetClass($assocName)
1918
    {
1919 4
        if (! isset($this->associationMappings[$assocName])) {
1920 2
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1921
        }
1922
1923 2
        return $this->associationMappings[$assocName]['targetDocument'];
1924
    }
1925
1926
    /**
1927
     * Retrieve the collectionClass associated with an association
1928
     *
1929
     * @param string $assocName
1930
     */
1931 1
    public function getAssociationCollectionClass($assocName)
1932
    {
1933 1
        if (! isset($this->associationMappings[$assocName])) {
1934
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1935
        }
1936
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
        }
1940
1941 1
        return $this->associationMappings[$assocName]['collectionClass'];
1942
    }
1943
1944
    /**
1945
     * {@inheritDoc}
1946
     */
1947
    public function isAssociationInverseSide($fieldName)
1948
    {
1949
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1950
    }
1951
1952
    /**
1953
     * {@inheritDoc}
1954
     */
1955
    public function getAssociationMappedByTargetField($fieldName)
1956
    {
1957
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1958
    }
1959
1960
    /**
1961
     * Map a field.
1962
     *
1963
     * @param array $mapping The mapping information.
1964
     *
1965
     * @return array
1966
     *
1967
     * @throws MappingException
1968
     */
1969 1421
    public function mapField(array $mapping)
1970
    {
1971 1421
        if (! isset($mapping['fieldName']) && isset($mapping['name'])) {
1972 9
            $mapping['fieldName'] = $mapping['name'];
1973
        }
1974 1421
        if (! isset($mapping['fieldName'])) {
1975
            throw MappingException::missingFieldName($this->name);
1976
        }
1977 1421
        if (! isset($mapping['name'])) {
1978 1412
            $mapping['name'] = $mapping['fieldName'];
1979
        }
1980 1421
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1981 1
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, $mapping['name']);
1982
        }
1983 1420
        if ($this->discriminatorField !== null && $this->discriminatorField === $mapping['name']) {
1984 1
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1985
        }
1986 1419
        if (isset($mapping['targetDocument']) && strpos($mapping['targetDocument'], '\\') === false && strlen($this->namespace)) {
1987 1112
            $mapping['targetDocument'] = $this->namespace . '\\' . $mapping['targetDocument'];
1988
        }
1989 1419
        if (isset($mapping['collectionClass'])) {
1990 56
            if (strpos($mapping['collectionClass'], '\\') === false && strlen($this->namespace)) {
1991 54
                $mapping['collectionClass'] = $this->namespace . '\\' . $mapping['collectionClass'];
1992
            }
1993 56
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1994
        }
1995 1419
        if (! empty($mapping['collectionClass'])) {
1996 56
            $rColl = new \ReflectionClass($mapping['collectionClass']);
1997 56
            if (! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1998 1
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1999
            }
2000
        }
2001
2002 1418
        if (isset($mapping['discriminatorMap'])) {
2003 117
            foreach ($mapping['discriminatorMap'] as $key => $class) {
2004 117
                if (strpos($class, '\\') !== false || ! strlen($this->namespace)) {
2005 97
                    continue;
2006
                }
2007
2008 66
                $mapping['discriminatorMap'][$key] = $this->namespace . '\\' . $class;
2009
            }
2010
        }
2011
2012 1418
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
2013 1
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
2014
        }
2015
2016 1417
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : [];
2017
2018 1417
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
2019 1121
            $cascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
2020
        }
2021
2022 1417
        if (isset($mapping['embedded'])) {
2023 1082
            unset($mapping['cascade']);
2024 1412
        } elseif (isset($mapping['cascade'])) {
2025 910
            $mapping['cascade'] = $cascades;
2026
        }
2027
2028 1417
        $mapping['isCascadeRemove'] = in_array('remove', $cascades);
2029 1417
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
2030 1417
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
2031 1417
        $mapping['isCascadeMerge'] = in_array('merge', $cascades);
2032 1417
        $mapping['isCascadeDetach'] = in_array('detach', $cascades);
2033
2034 1417
        if (isset($mapping['id']) && $mapping['id'] === true) {
2035 1385
            $mapping['name'] = '_id';
2036 1385
            $this->identifier = $mapping['fieldName'];
2037 1385
            if (isset($mapping['strategy'])) {
2038 1378
                $this->generatorType = constant(self::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
2039
            }
2040 1385
            $this->generatorOptions = $mapping['options'] ?? [];
2041 1385
            switch ($this->generatorType) {
2042 1385
                case self::GENERATOR_TYPE_AUTO:
2043 1313
                    $mapping['type'] = 'id';
2044 1313
                    break;
2045
                default:
2046 148
                    if (! empty($this->generatorOptions['type'])) {
2047 56
                        $mapping['type'] = $this->generatorOptions['type'];
2048 92
                    } elseif (empty($mapping['type'])) {
2049 80
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
2050
                    }
2051
            }
2052 1385
            unset($this->generatorOptions['type']);
2053
        }
2054
2055 1417
        if (! isset($mapping['nullable'])) {
2056 40
            $mapping['nullable'] = false;
2057
        }
2058
2059 1417
        if (isset($mapping['reference'])
2060 1417
            && isset($mapping['storeAs'])
2061 1417
            && $mapping['storeAs'] === self::REFERENCE_STORE_AS_ID
2062 1417
            && ! isset($mapping['targetDocument'])
2063
        ) {
2064 3
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
2065
        }
2066
2067 1414
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
2068 1414
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
2069 4
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
2070
        }
2071
2072 1410
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
2073 1
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
2074
        }
2075
2076 1409
        if (isset($mapping['repositoryMethod']) && ! (empty($mapping['skip']) && empty($mapping['limit']) && empty($mapping['sort']))) {
2077 3
            throw MappingException::repositoryMethodCanNotBeCombinedWithSkipLimitAndSort($this->name, $mapping['fieldName']);
2078
        }
2079
2080 1406
        if (isset($mapping['reference']) && $mapping['type'] === 'one') {
2081 1006
            $mapping['association'] = self::REFERENCE_ONE;
2082
        }
2083 1406
        if (isset($mapping['reference']) && $mapping['type'] === 'many') {
2084 960
            $mapping['association'] = self::REFERENCE_MANY;
2085
        }
2086 1406
        if (isset($mapping['embedded']) && $mapping['type'] === 'one') {
2087 959
            $mapping['association'] = self::EMBED_ONE;
2088
        }
2089 1406
        if (isset($mapping['embedded']) && $mapping['type'] === 'many') {
2090 983
            $mapping['association'] = self::EMBED_MANY;
2091
        }
2092
2093 1406
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
2094 127
            $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
2095
        }
2096
2097 1406
        if (isset($mapping['version'])) {
2098 70
            $mapping['notSaved'] = true;
2099 70
            $this->setVersionMapping($mapping);
2100
        }
2101 1406
        if (isset($mapping['lock'])) {
2102 22
            $mapping['notSaved'] = true;
2103 22
            $this->setLockMapping($mapping);
2104
        }
2105 1406
        $mapping['isOwningSide'] = true;
2106 1406
        $mapping['isInverseSide'] = false;
2107 1406
        if (isset($mapping['reference'])) {
2108 1071
            if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
2109 87
                $mapping['isOwningSide'] = true;
2110 87
                $mapping['isInverseSide'] = false;
2111
            }
2112 1071
            if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
2113 819
                $mapping['isInverseSide'] = true;
2114 819
                $mapping['isOwningSide'] = false;
2115
            }
2116 1071
            if (isset($mapping['repositoryMethod'])) {
2117 61
                $mapping['isInverseSide'] = true;
2118 61
                $mapping['isOwningSide'] = false;
2119
            }
2120 1071
            if (! isset($mapping['orphanRemoval'])) {
2121 1051
                $mapping['orphanRemoval'] = false;
2122
            }
2123
        }
2124
2125 1406
        if (! empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || ! $mapping['isInverseSide'])) {
2126
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
2127
        }
2128
2129 1406
        if ($this->isFile && ! $this->isAllowedGridFSField($mapping['name'])) {
2130 1
            throw MappingException::fieldNotAllowedForGridFS($this->name, $mapping['fieldName']);
2131
        }
2132
2133 1405
        $this->applyStorageStrategy($mapping);
2134
2135 1404
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
2136 1404
        if (isset($mapping['association'])) {
2137 1213
            $this->associationMappings[$mapping['fieldName']] = $mapping;
2138
        }
2139
2140 1404
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
2141 1403
        $reflProp->setAccessible(true);
2142 1403
        $this->reflFields[$mapping['fieldName']] = $reflProp;
2143
2144 1403
        return $mapping;
2145
    }
2146
2147
    /**
2148
     * Determines which fields get serialized.
2149
     *
2150
     * It is only serialized what is necessary for best unserialization performance.
2151
     * That means any metadata properties that are not set or empty or simply have
2152
     * their default value are NOT serialized.
2153
     *
2154
     * Parts that are also NOT serialized because they can not be properly unserialized:
2155
     *      - reflClass (ReflectionClass)
2156
     *      - reflFields (ReflectionProperty array)
2157
     *
2158
     * @return array The names of all the fields that should be serialized.
2159
     */
2160 6
    public function __sleep()
2161
    {
2162
        // This metadata is always serialized/cached.
2163
        $serialized = [
2164 6
            'fieldMappings',
2165
            'associationMappings',
2166
            'identifier',
2167
            'name',
2168
            'namespace', // TODO: REMOVE
2169
            'db',
2170
            'collection',
2171
            'readPreference',
2172
            'readPreferenceTags',
2173
            'writeConcern',
2174
            'rootDocumentName',
2175
            'generatorType',
2176
            'generatorOptions',
2177
            'idGenerator',
2178
            'indexes',
2179
            'shardKey',
2180
        ];
2181
2182
        // The rest of the metadata is only serialized if necessary.
2183 6
        if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {
2184
            $serialized[] = 'changeTrackingPolicy';
2185
        }
2186
2187 6
        if ($this->customRepositoryClassName) {
2188 1
            $serialized[] = 'customRepositoryClassName';
2189
        }
2190
2191 6
        if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE || $this->discriminatorField !== null) {
2192 4
            $serialized[] = 'inheritanceType';
2193 4
            $serialized[] = 'discriminatorField';
2194 4
            $serialized[] = 'discriminatorValue';
2195 4
            $serialized[] = 'discriminatorMap';
2196 4
            $serialized[] = 'defaultDiscriminatorValue';
2197 4
            $serialized[] = 'parentClasses';
2198 4
            $serialized[] = 'subClasses';
2199
        }
2200
2201 6
        if ($this->isMappedSuperclass) {
2202 1
            $serialized[] = 'isMappedSuperclass';
2203
        }
2204
2205 6
        if ($this->isEmbeddedDocument) {
2206 1
            $serialized[] = 'isEmbeddedDocument';
2207
        }
2208
2209 6
        if ($this->isQueryResultDocument) {
2210
            $serialized[] = 'isQueryResultDocument';
2211
        }
2212
2213 6
        if ($this->isFile) {
2214
            $serialized[] = 'isFile';
2215
            $serialized[] = 'bucketName';
2216
        }
2217
2218 6
        if ($this->isVersioned) {
2219
            $serialized[] = 'isVersioned';
2220
            $serialized[] = 'versionField';
2221
        }
2222
2223 6
        if ($this->lifecycleCallbacks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->lifecycleCallbacks of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2224
            $serialized[] = 'lifecycleCallbacks';
2225
        }
2226
2227 6
        if ($this->collectionCapped) {
2228 1
            $serialized[] = 'collectionCapped';
2229 1
            $serialized[] = 'collectionSize';
2230 1
            $serialized[] = 'collectionMax';
2231
        }
2232
2233 6
        if ($this->isReadOnly) {
2234
            $serialized[] = 'isReadOnly';
2235
        }
2236
2237 6
        return $serialized;
2238
    }
2239
2240
    /**
2241
     * Restores some state that can not be serialized/unserialized.
2242
     *
2243
     */
2244 6
    public function __wakeup()
2245
    {
2246
        // Restore ReflectionClass and properties
2247 6
        $this->reflClass = new \ReflectionClass($this->name);
2248 6
        $this->instantiator = $this->instantiator ?: new Instantiator();
2249
2250 6
        foreach ($this->fieldMappings as $field => $mapping) {
2251 3
            if (isset($mapping['declared'])) {
2252 1
                $reflField = new \ReflectionProperty($mapping['declared'], $field);
2253
            } else {
2254 3
                $reflField = $this->reflClass->getProperty($field);
2255
            }
2256 3
            $reflField->setAccessible(true);
2257 3
            $this->reflFields[$field] = $reflField;
2258
        }
2259 6
    }
2260
2261
    /**
2262
     * Creates a new instance of the mapped class, without invoking the constructor.
2263
     *
2264
     * @return object
2265
     */
2266 358
    public function newInstance()
2267
    {
2268 358
        return $this->instantiator->instantiate($this->name);
2269
    }
2270
2271 59
    private function isAllowedGridFSField(string $name): bool
2272
    {
2273 59
        return in_array($name, self::ALLOWED_GRIDFS_FIELDS, true);
2274
    }
2275
}
2276