Completed
Pull Request — master (#1790)
by Andreas
17:36
created

ClassMetadata::setDatabase()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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