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

ClassMetadata::setIdGenerator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

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