Completed
Pull Request — master (#1887)
by
unknown
15:03 queued 09:19
created

ClassMetadata::newInstance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Mapping;
6
7
use BadMethodCallException;
8
use Doctrine\Common\Persistence\Mapping\ClassMetadata as BaseClassMetadata;
9
use Doctrine\Instantiator\Instantiator;
10
use Doctrine\Instantiator\InstantiatorInterface;
11
use Doctrine\ODM\MongoDB\Id\AbstractIdGenerator;
12
use Doctrine\ODM\MongoDB\LockException;
13
use Doctrine\ODM\MongoDB\Proxy\Proxy;
14
use Doctrine\ODM\MongoDB\Types\Type;
15
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
16
use InvalidArgumentException;
17
use LogicException;
18
use ReflectionClass;
19
use ReflectionProperty;
20
use function array_filter;
21
use function array_key_exists;
22
use function array_keys;
23
use function array_map;
24
use function array_pop;
25
use function call_user_func_array;
26
use function class_exists;
27
use function constant;
28
use function count;
29
use function get_class;
30
use function in_array;
31
use function interface_exists;
32
use function is_array;
33
use function is_string;
34
use function is_subclass_of;
35
use function ltrim;
36
use function sprintf;
37
use function strtolower;
38
use function strtoupper;
39
40
/**
41
 * A <tt>ClassMetadata</tt> instance holds all the object-document mapping metadata
42
 * of a document and it's references.
43
 *
44
 * Once populated, ClassMetadata instances are usually cached in a serialized form.
45
 *
46
 * <b>IMPORTANT NOTE:</b>
47
 *
48
 * The fields of this class are only public for 2 reasons:
49
 * 1) To allow fast READ access.
50
 * 2) To drastically reduce the size of a serialized instance (private/protected members
51
 *    get the whole class name, namespace inclusive, prepended to every property in
52
 *    the serialized representation).
53
 */
54
class ClassMetadata implements BaseClassMetadata
55
{
56
    /* The Id generator types. */
57
    /**
58
     * AUTO means Doctrine will automatically create a new \MongoDB\BSON\ObjectId instance for us.
59
     */
60
    public const GENERATOR_TYPE_AUTO = 1;
61
62
    /**
63
     * INCREMENT means a separate collection is used for maintaining and incrementing id generation.
64
     * Offers full portability.
65
     */
66
    public const GENERATOR_TYPE_INCREMENT = 2;
67
68
    /**
69
     * UUID means Doctrine will generate a uuid for us.
70
     */
71
    public const GENERATOR_TYPE_UUID = 3;
72
73
    /**
74
     * ALNUM means Doctrine will generate Alpha-numeric string identifiers, using the INCREMENT
75
     * generator to ensure identifier uniqueness
76
     */
77
    public const GENERATOR_TYPE_ALNUM = 4;
78
79
    /**
80
     * CUSTOM means Doctrine expect a class parameter. It will then try to initiate that class
81
     * and pass other options to the generator. It will throw an Exception if the class
82
     * does not exist or if an option was passed for that there is not setter in the new
83
     * generator class.
84
     *
85
     * The class  will have to be a subtype of AbstractIdGenerator.
86
     */
87
    public const GENERATOR_TYPE_CUSTOM = 5;
88
89
    /**
90
     * NONE means Doctrine will not generate any id for us and you are responsible for manually
91
     * assigning an id.
92
     */
93
    public const GENERATOR_TYPE_NONE = 6;
94
95
    /**
96
     * Default discriminator field name.
97
     *
98
     * This is used for associations value for associations where a that do not define a "targetDocument" or
99
     * "discriminatorField" option in their mapping.
100
     */
101
    public const DEFAULT_DISCRIMINATOR_FIELD = '_doctrine_class_name';
102
103
    public const REFERENCE_ONE  = 1;
104
    public const REFERENCE_MANY = 2;
105
    public const EMBED_ONE      = 3;
106
    public const EMBED_MANY     = 4;
107
    public const MANY           = 'many';
108
    public const ONE            = 'one';
109
110
    /**
111
     * The types of storeAs references
112
     */
113
    public const REFERENCE_STORE_AS_ID             = 'id';
114
    public const REFERENCE_STORE_AS_DB_REF         = 'dbRef';
115
    public const REFERENCE_STORE_AS_DB_REF_WITH_DB = 'dbRefWithDb';
116
    public const REFERENCE_STORE_AS_REF            = 'ref';
117
118
    /* The inheritance mapping types */
119
    /**
120
     * NONE means the class does not participate in an inheritance hierarchy
121
     * and therefore does not need an inheritance mapping type.
122
     */
123
    public const INHERITANCE_TYPE_NONE = 1;
124
125
    /**
126
     * SINGLE_COLLECTION means the class will be persisted according to the rules of
127
     * <tt>Single Collection Inheritance</tt>.
128
     */
129
    public const INHERITANCE_TYPE_SINGLE_COLLECTION = 2;
130
131
    /**
132
     * COLLECTION_PER_CLASS means the class will be persisted according to the rules
133
     * of <tt>Concrete Collection Inheritance</tt>.
134
     */
135
    public const INHERITANCE_TYPE_COLLECTION_PER_CLASS = 3;
136
137
    /**
138
     * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time
139
     * by doing a property-by-property comparison with the original data. This will
140
     * be done for all entities that are in MANAGED state at commit-time.
141
     *
142
     * This is the default change tracking policy.
143
     */
144
    public const CHANGETRACKING_DEFERRED_IMPLICIT = 1;
145
146
    /**
147
     * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time
148
     * by doing a property-by-property comparison with the original data. This will
149
     * be done only for entities that were explicitly saved (through persist() or a cascade).
150
     */
151
    public const CHANGETRACKING_DEFERRED_EXPLICIT = 2;
152
153
    /**
154
     * NOTIFY means that Doctrine relies on the entities sending out notifications
155
     * when their properties change. Such entity classes must implement
156
     * the <tt>NotifyPropertyChanged</tt> interface.
157
     */
158
    public const CHANGETRACKING_NOTIFY = 3;
159
160
    /**
161
     * SET means that fields will be written to the database using a $set operator
162
     */
163
    public const STORAGE_STRATEGY_SET = 'set';
164
165
    /**
166
     * INCREMENT means that fields will be written to the database by calculating
167
     * the difference and using the $inc operator
168
     */
169
    public const STORAGE_STRATEGY_INCREMENT = 'increment';
170
171
    public const STORAGE_STRATEGY_PUSH_ALL         = 'pushAll';
172
    public const STORAGE_STRATEGY_ADD_TO_SET       = 'addToSet';
173
    public const STORAGE_STRATEGY_ATOMIC_SET       = 'atomicSet';
174
    public const STORAGE_STRATEGY_ATOMIC_SET_ARRAY = 'atomicSetArray';
175
    public const STORAGE_STRATEGY_SET_ARRAY        = 'setArray';
176
177
    private const ALLOWED_GRIDFS_FIELDS = ['_id', 'chunkSize', 'filename', 'length', 'metadata', 'uploadDate'];
178
179
    /**
180
     * READ-ONLY: The name of the mongo database the document is mapped to.
181
     *
182
     * @var string|null
183
     */
184
    public $db;
185
186
    /**
187
     * READ-ONLY: The name of the mongo collection the document is mapped to.
188
     *
189
     * @var string
190
     */
191
    public $collection;
192
193
    /**
194
     * READ-ONLY: The name of the GridFS bucket the document is mapped to.
195
     *
196
     * @var string|null
197
     */
198
    public $bucketName;
199
200
    /**
201
     * READ-ONLY: If the collection should be a fixed size.
202
     *
203
     * @var bool
204
     */
205
    public $collectionCapped = false;
206
207
    /**
208
     * READ-ONLY: If the collection is fixed size, its size in bytes.
209
     *
210
     * @var int|null
211
     */
212
    public $collectionSize;
213
214
    /**
215
     * READ-ONLY: If the collection is fixed size, the maximum number of elements to store in the collection.
216
     *
217
     * @var int|null
218
     */
219
    public $collectionMax;
220
221
    /**
222
     * READ-ONLY Describes how MongoDB clients route read operations to the members of a replica set.
223
     *
224
     * @var string|int|null
225
     */
226
    public $readPreference;
227
228
    /**
229
     * READ-ONLY Associated with readPreference Allows to specify criteria so that your application can target read
230
     * operations to specific members, based on custom parameters.
231
     *
232
     * @var string[][]|null
233
     */
234
    public $readPreferenceTags;
235
236
    /**
237
     * READ-ONLY: Describes the level of acknowledgement requested from MongoDB for write operations.
238
     *
239
     * @var string|int|null
240
     */
241
    public $writeConcern;
242
243
    /**
244
     * READ-ONLY: The field name of the document identifier.
245
     *
246
     * @var string|null
247
     */
248
    public $identifier;
249
250
    /**
251
     * READ-ONLY: The array of indexes for the document collection.
252
     *
253
     * @var array
254
     */
255
    public $indexes = [];
256
257
    /**
258
     * READ-ONLY: Keys and options describing shard key. Only for sharded collections.
259
     *
260
     * @var string|null
261
     */
262
    public $shardKey;
263
264
    /**
265
     * READ-ONLY: The name of the document class.
266
     *
267
     * @var string
268
     */
269
    public $name;
270
271
    /**
272
     * READ-ONLY: The name of the document class that is at the root of the mapped document inheritance
273
     * hierarchy. If the document is not part of a mapped inheritance hierarchy this is the same
274
     * as {@link $documentName}.
275
     *
276
     * @var string
277
     */
278
    public $rootDocumentName;
279
280
    /**
281
     * The name of the custom repository class used for the document class.
282
     * (Optional).
283
     *
284
     * @var string|null
285
     */
286
    public $customRepositoryClassName;
287
288
    /**
289
     * READ-ONLY: The names of the parent classes (ancestors).
290
     *
291
     * @var array
292
     */
293
    public $parentClasses = [];
294
295
    /**
296
     * READ-ONLY: The names of all subclasses (descendants).
297
     *
298
     * @var array
299
     */
300
    public $subClasses = [];
301
302
    /**
303
     * The ReflectionProperty instances of the mapped class.
304
     *
305
     * @var ReflectionProperty[]
306
     */
307
    public $reflFields = [];
308
309
    /**
310
     * READ-ONLY: The inheritance mapping type used by the class.
311
     *
312
     * @var int
313
     */
314
    public $inheritanceType = self::INHERITANCE_TYPE_NONE;
315
316
    /**
317
     * READ-ONLY: The Id generator type used by the class.
318
     *
319
     * @var int
320
     */
321
    public $generatorType = self::GENERATOR_TYPE_AUTO;
322
323
    /**
324
     * READ-ONLY: The Id generator options.
325
     *
326
     * @var array
327
     */
328
    public $generatorOptions = [];
329
330
    /**
331
     * READ-ONLY: The ID generator used for generating IDs for this class.
332
     *
333
     * @var AbstractIdGenerator
334
     */
335
    public $idGenerator;
336
337
    /**
338
     * READ-ONLY: The field mappings of the class.
339
     * Keys are field names and values are mapping definitions.
340
     *
341
     * The mapping definition array has the following values:
342
     *
343
     * - <b>fieldName</b> (string)
344
     * The name of the field in the Document.
345
     *
346
     * - <b>id</b> (boolean, optional)
347
     * Marks the field as the primary key of the document. Multiple fields of an
348
     * document can have the id attribute, forming a composite key.
349
     *
350
     * @var array
351
     */
352
    public $fieldMappings = [];
353
354
    /**
355
     * READ-ONLY: The association mappings of the class.
356
     * Keys are field names and values are mapping definitions.
357
     *
358
     * @var array
359
     */
360
    public $associationMappings = [];
361
362
    /**
363
     * READ-ONLY: Array of fields to also load with a given method.
364
     *
365
     * @var array
366
     */
367
    public $alsoLoadMethods = [];
368
369
    /**
370
     * READ-ONLY: The registered lifecycle callbacks for documents of this class.
371
     *
372
     * @var array
373
     */
374
    public $lifecycleCallbacks = [];
375
376
    /**
377
     * READ-ONLY: The discriminator value of this class.
378
     *
379
     * <b>This does only apply to the JOINED and SINGLE_COLLECTION inheritance mapping strategies
380
     * where a discriminator field is used.</b>
381
     *
382
     * @see discriminatorField
383
     *
384
     * @var mixed
385
     */
386
    public $discriminatorValue;
387
388
    /**
389
     * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
390
     *
391
     * <b>This does only apply to the SINGLE_COLLECTION inheritance mapping strategy
392
     * where a discriminator field is used.</b>
393
     *
394
     * @see discriminatorField
395
     *
396
     * @var mixed
397
     */
398
    public $discriminatorMap = [];
399
400
    /**
401
     * READ-ONLY: The definition of the discriminator field used in SINGLE_COLLECTION
402
     * inheritance mapping.
403
     *
404
     * @var string
405
     */
406
    public $discriminatorField;
407
408
    /**
409
     * READ-ONLY: The default value for discriminatorField in case it's not set in the document
410
     *
411
     * @see discriminatorField
412
     *
413
     * @var string
414
     */
415
    public $defaultDiscriminatorValue;
416
417
    /**
418
     * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
419
     *
420
     * @var bool
421
     */
422
    public $isMappedSuperclass = false;
423
424
    /**
425
     * READ-ONLY: Whether this class describes the mapping of a embedded document.
426
     *
427
     * @var bool
428
     */
429
    public $isEmbeddedDocument = false;
430
431
    /**
432
     * READ-ONLY: Whether this class describes the mapping of an aggregation result document.
433
     *
434
     * @var bool
435
     */
436
    public $isQueryResultDocument = false;
437
438
    /**
439
     * READ-ONLY: Whether this class describes the mapping of a gridFS file
440
     *
441
     * @var bool
442
     */
443
    public $isFile = false;
444
445
    /**
446
     * READ-ONLY: The default chunk size in bytes for the file
447
     *
448
     * @var int|null
449
     */
450
    public $chunkSizeBytes;
451
452
    /**
453
     * READ-ONLY: The policy used for change-tracking on entities of this class.
454
     *
455
     * @var int
456
     */
457
    public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;
458
459
    /**
460
     * READ-ONLY: A flag for whether or not instances of this class are to be versioned
461
     * with optimistic locking.
462
     *
463
     * @var bool $isVersioned
464
     */
465
    public $isVersioned = false;
466
467
    /**
468
     * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
469
     *
470
     * @var string|null $versionField
471
     */
472
    public $versionField;
473
474
    /**
475
     * READ-ONLY: A flag for whether or not instances of this class are to allow pessimistic
476
     * locking.
477
     *
478
     * @var bool $isLockable
479
     */
480
    public $isLockable = false;
481
482
    /**
483
     * READ-ONLY: The name of the field which is used for locking a document.
484
     *
485
     * @var mixed $lockField
486
     */
487
    public $lockField;
488
489
    /**
490
     * The ReflectionClass instance of the mapped class.
491
     *
492
     * @var ReflectionClass
493
     */
494
    public $reflClass;
495
496
    /**
497
     * READ_ONLY: A flag for whether or not this document is read-only.
498
     *
499
     * @var bool
500
     */
501
    public $isReadOnly;
502
503
    /** @var InstantiatorInterface|null */
504
    private $instantiator;
505
506
    /**
507
     * Initializes a new ClassMetadata instance that will hold the object-document mapping
508
     * metadata of the class with the given name.
509
     */
510 1551
    public function __construct(string $documentName)
511
    {
512 1551
        $this->name             = $documentName;
513 1551
        $this->rootDocumentName = $documentName;
514 1551
        $this->reflClass        = new ReflectionClass($documentName);
515 1551
        $this->setCollection($this->reflClass->getShortName());
516 1551
        $this->instantiator = new Instantiator();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Doctrine\Instantiator\Instantiator() of type object<Doctrine\Instantiator\Instantiator> is incompatible with the declared type object<Doctrine\Instanti...antiatorInterface>|null of property $instantiator.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
517 1551
    }
518
519
    /**
520
     * Helper method to get reference id of ref* type references
521
     *
522
     * @internal
523
     *
524
     * @param mixed $reference
525
     *
526
     * @return mixed
527
     */
528 117
    public static function getReferenceId($reference, string $storeAs)
529
    {
530 117
        return $storeAs === self::REFERENCE_STORE_AS_ID ? $reference : $reference[self::getReferencePrefix($storeAs) . 'id'];
531
    }
532
533
    /**
534
     * Returns the reference prefix used for a reference
535
     */
536 185
    private static function getReferencePrefix(string $storeAs) : string
537
    {
538 185
        if (! in_array($storeAs, [self::REFERENCE_STORE_AS_REF, self::REFERENCE_STORE_AS_DB_REF, self::REFERENCE_STORE_AS_DB_REF_WITH_DB])) {
539
            throw new LogicException('Can only get a reference prefix for DBRef and reference arrays');
540
        }
541
542 185
        return $storeAs === self::REFERENCE_STORE_AS_REF ? '' : '$';
543
    }
544
545
    /**
546
     * Returns a fully qualified field name for a given reference
547
     *
548
     * @internal
549
     *
550
     * @param string $pathPrefix The field path prefix
551
     */
552 137
    public static function getReferenceFieldName(string $storeAs, string $pathPrefix = '') : string
553
    {
554 137
        if ($storeAs === self::REFERENCE_STORE_AS_ID) {
555 97
            return $pathPrefix;
556
        }
557
558 125
        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...
559
    }
560
561
    /**
562
     * {@inheritDoc}
563
     */
564 1436
    public function getReflectionClass() : ReflectionClass
565
    {
566 1436
        if (! $this->reflClass) {
567
            $this->reflClass = new ReflectionClass($this->name);
568
        }
569
570 1436
        return $this->reflClass;
571
    }
572
573
    /**
574
     * {@inheritDoc}
575
     */
576 333
    public function isIdentifier($fieldName) : bool
577
    {
578 333
        return $this->identifier === $fieldName;
579
    }
580
581
    /**
582
     * INTERNAL:
583
     * Sets the mapped identifier field of this class.
584
     */
585 916
    public function setIdentifier(?string $identifier) : void
586
    {
587 916
        $this->identifier = $identifier;
588 916
    }
589
590
    /**
591
     * {@inheritDoc}
592
     *
593
     * Since MongoDB only allows exactly one identifier field
594
     * this will always return an array with only one value
595
     */
596 39
    public function getIdentifier() : array
597
    {
598 39
        return [$this->identifier];
599
    }
600
601
    /**
602
     * {@inheritDoc}
603
     *
604
     * Since MongoDB only allows exactly one identifier field
605
     * this will always return an array with only one value
606
     */
607 100
    public function getIdentifierFieldNames() : array
608
    {
609 100
        return [$this->identifier];
610
    }
611
612
    /**
613
     * {@inheritDoc}
614
     */
615 912
    public function hasField($fieldName) : bool
616
    {
617 912
        return isset($this->fieldMappings[$fieldName]);
618
    }
619
620
    /**
621
     * Sets the inheritance type used by the class and it's subclasses.
622
     */
623 932
    public function setInheritanceType(int $type) : void
624
    {
625 932
        $this->inheritanceType = $type;
626 932
    }
627
628
    /**
629
     * Checks whether a mapped field is inherited from an entity superclass.
630
     */
631 1432
    public function isInheritedField(string $fieldName) : bool
632
    {
633 1432
        return isset($this->fieldMappings[$fieldName]['inherited']);
634
    }
635
636
    /**
637
     * Registers a custom repository class for the document class.
638
     */
639 864
    public function setCustomRepositoryClass(?string $repositoryClassName) : void
640
    {
641 864
        if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
642
            return;
643
        }
644
645 864
        $this->customRepositoryClassName = $repositoryClassName;
646 864
    }
647
648
    /**
649
     * Dispatches the lifecycle event of the given document by invoking all
650
     * registered callbacks.
651
     *
652
     * @throws InvalidArgumentException If document class is not this class or
653
     *                                   a Proxy of this class.
654
     */
655 637
    public function invokeLifecycleCallbacks(string $event, object $document, ?array $arguments = null) : void
656
    {
657 637
        if (! $document instanceof $this->name) {
658 1
            throw new InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"', $this->name, get_class($document)));
659
        }
660
661 636
        if (empty($this->lifecycleCallbacks[$event])) {
662 621
            return;
663
        }
664
665 189
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
666 189
            if ($arguments !== null) {
667 188
                call_user_func_array([$document, $callback], $arguments);
668
            } else {
669 189
                $document->$callback();
670
            }
671
        }
672 189
    }
673
674
    /**
675
     * Checks whether the class has callbacks registered for a lifecycle event.
676
     */
677
    public function hasLifecycleCallbacks(string $event) : bool
678
    {
679
        return ! empty($this->lifecycleCallbacks[$event]);
680
    }
681
682
    /**
683
     * Gets the registered lifecycle callbacks for an event.
684
     */
685
    public function getLifecycleCallbacks(string $event) : array
686
    {
687
        return $this->lifecycleCallbacks[$event] ?? [];
688
    }
689
690
    /**
691
     * Adds a lifecycle callback for documents of this class.
692
     *
693
     * If the callback is already registered, this is a NOOP.
694
     */
695 835
    public function addLifecycleCallback(string $callback, string $event) : void
696
    {
697 835
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
698 1
            return;
699
        }
700
701 835
        $this->lifecycleCallbacks[$event][] = $callback;
702 835
    }
703
704
    /**
705
     * Sets the lifecycle callbacks for documents of this class.
706
     *
707
     * Any previously registered callbacks are overwritten.
708
     */
709 915
    public function setLifecycleCallbacks(array $callbacks) : void
710
    {
711 915
        $this->lifecycleCallbacks = $callbacks;
712 915
    }
713
714
    /**
715
     * Registers a method for loading document data before field hydration.
716
     *
717
     * Note: A method may be registered multiple times for different fields.
718
     * it will be invoked only once for the first field found.
719
     *
720
     * @param array|string $fields Database field name(s)
721
     */
722 14
    public function registerAlsoLoadMethod(string $method, $fields) : void
723
    {
724 14
        $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : [$fields];
725 14
    }
726
727
    /**
728
     * Sets the AlsoLoad methods for documents of this class.
729
     *
730
     * Any previously registered methods are overwritten.
731
     */
732 915
    public function setAlsoLoadMethods(array $methods) : void
733
    {
734 915
        $this->alsoLoadMethods = $methods;
735 915
    }
736
737
    /**
738
     * Sets the discriminator field.
739
     *
740
     * The field name is the the unmapped database field. Discriminator values
741
     * are only used to discern the hydration class and are not mapped to class
742
     * properties.
743
     *
744
     * @param string|array $discriminatorField
745
     *
746
     * @throws MappingException If the discriminator field conflicts with the
747
     *                          "name" attribute of a mapped field.
748
     */
749 941
    public function setDiscriminatorField($discriminatorField) : void
750
    {
751 941
        if ($this->isFile) {
752
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
753
        }
754
755 941
        if ($discriminatorField === null) {
756 873
            $this->discriminatorField = null;
757
758 873
            return;
759
        }
760
761
        // Handle array argument with name/fieldName keys for BC
762 137
        if (is_array($discriminatorField)) {
763
            if (isset($discriminatorField['name'])) {
764
                $discriminatorField = $discriminatorField['name'];
765
            } elseif (isset($discriminatorField['fieldName'])) {
766
                $discriminatorField = $discriminatorField['fieldName'];
767
            }
768
        }
769
770 137
        foreach ($this->fieldMappings as $fieldMapping) {
771 4
            if ($discriminatorField === $fieldMapping['name']) {
772 4
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
773
            }
774
        }
775
776 136
        $this->discriminatorField = $discriminatorField;
777 136
    }
778
779
    /**
780
     * Sets the discriminator values used by this class.
781
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
782
     *
783
     * @throws MappingException
784
     */
785 934
    public function setDiscriminatorMap(array $map) : void
786
    {
787 934
        if ($this->isFile) {
788
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
789
        }
790
791 934
        foreach ($map as $value => $className) {
792 132
            $this->discriminatorMap[$value] = $className;
793 132
            if ($this->name === $className) {
794 124
                $this->discriminatorValue = $value;
795
            } else {
796 131
                if (! class_exists($className)) {
797
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
798
                }
799 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...
800 132
                    $this->subClasses[] = $className;
801
                }
802
            }
803
        }
804 934
    }
805
806
    /**
807
     * Sets the default discriminator value to be used for this class
808
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies if the document has no discriminator value
809
     *
810
     * @throws MappingException
811
     */
812 918
    public function setDefaultDiscriminatorValue(?string $defaultDiscriminatorValue) : void
813
    {
814 918
        if ($this->isFile) {
815
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
816
        }
817
818 918
        if ($defaultDiscriminatorValue === null) {
819 915
            $this->defaultDiscriminatorValue = null;
820
821 915
            return;
822
        }
823
824 68
        if (! array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
825
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
826
        }
827
828 68
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
829 68
    }
830
831
    /**
832
     * Sets the discriminator value for this class.
833
     * Used for JOINED/SINGLE_TABLE inheritance and multiple document types in a single
834
     * collection.
835
     *
836
     * @throws MappingException
837
     */
838 3
    public function setDiscriminatorValue(string $value) : void
839
    {
840 3
        if ($this->isFile) {
841
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
842
        }
843
844 3
        $this->discriminatorMap[$value] = $this->name;
845 3
        $this->discriminatorValue       = $value;
846 3
    }
847
848
    /**
849
     * Add a index for this Document.
850
     */
851 217
    public function addIndex(array $keys, array $options = []) : void
852
    {
853 217
        $this->indexes[] = [
854
            'keys' => array_map(static function ($value) {
855 217
                if ($value === 1 || $value === -1) {
856 65
                    return (int) $value;
857
                }
858 217
                if (is_string($value)) {
859 217
                    $lower = strtolower($value);
860 217
                    if ($lower === 'asc') {
861 210
                        return 1;
862
                    }
863
864 72
                    if ($lower === 'desc') {
865
                        return -1;
866
                    }
867
                }
868 72
                return $value;
869 217
            }, $keys),
870 217
            'options' => $options,
871
        ];
872 217
    }
873
874
    /**
875
     * Returns the array of indexes for this Document.
876
     */
877 26
    public function getIndexes() : array
878
    {
879 26
        return $this->indexes;
880
    }
881
882
    /**
883
     * Checks whether this document has indexes or not.
884
     */
885
    public function hasIndexes() : bool
886
    {
887
        return $this->indexes ? true : false;
888
    }
889
890
    /**
891
     * Set shard key for this Document.
892
     *
893
     * @throws MappingException
894
     */
895 100
    public function setShardKey(array $keys, array $options = []) : void
896
    {
897 100
        if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && $this->shardKey !== null) {
898 2
            throw MappingException::shardKeyInSingleCollInheritanceSubclass($this->getName());
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
899
        }
900
901 100
        if ($this->isEmbeddedDocument) {
902 2
            throw MappingException::embeddedDocumentCantHaveShardKey($this->getName());
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
903
        }
904
905 98
        foreach (array_keys($keys) as $field) {
906 98
            if (! isset($this->fieldMappings[$field])) {
907 91
                continue;
908
            }
909
910 7
            if (in_array($this->fieldMappings[$field]['type'], ['many', 'collection'])) {
911 3
                throw MappingException::noMultiKeyShardKeys($this->getName(), $field);
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
912
            }
913
914 4
            if ($this->fieldMappings[$field]['strategy'] !== static::STORAGE_STRATEGY_SET) {
915 4
                throw MappingException::onlySetStrategyAllowedInShardKey($this->getName(), $field);
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
916
            }
917
        }
918
919 94
        $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...
920
            'keys' => array_map(static function ($value) {
921 94
                if ($value === 1 || $value === -1) {
922 5
                    return (int) $value;
923
                }
924 94
                if (is_string($value)) {
925 94
                    $lower = strtolower($value);
926 94
                    if ($lower === 'asc') {
927 92
                        return 1;
928
                    }
929
930 67
                    if ($lower === 'desc') {
931
                        return -1;
932
                    }
933
                }
934 67
                return $value;
935 94
            }, $keys),
936 94
            'options' => $options,
937
        ];
938 94
    }
939
940 26
    public function getShardKey() : array
941
    {
942 26
        return $this->shardKey;
943
    }
944
945
    /**
946
     * Checks whether this document has shard key or not.
947
     */
948 1153
    public function isSharded() : bool
949
    {
950 1153
        return $this->shardKey ? true : false;
951
    }
952
953
    /**
954
     * Sets the read preference used by this class.
955
     *
956
     * @param string|int|null $readPreference
957
     * @param array|null      $tags
958
     */
959 915
    public function setReadPreference($readPreference, $tags) : void
960
    {
961 915
        $this->readPreference     = $readPreference;
962 915
        $this->readPreferenceTags = $tags;
963 915
    }
964
965
    /**
966
     * Sets the write concern used by this class.
967
     *
968
     * @param string|int|null $writeConcern
969
     */
970 925
    public function setWriteConcern($writeConcern) : void
971
    {
972 925
        $this->writeConcern = $writeConcern;
973 925
    }
974
975
    /**
976
     * @return int|string|null
977
     */
978 11
    public function getWriteConcern()
979
    {
980 11
        return $this->writeConcern;
981
    }
982
983
    /**
984
     * Whether there is a write concern configured for this class.
985
     */
986 584
    public function hasWriteConcern() : bool
987
    {
988 584
        return $this->writeConcern !== null;
989
    }
990
991
    /**
992
     * Sets the change tracking policy used by this class.
993
     */
994 917
    public function setChangeTrackingPolicy(int $policy) : void
995
    {
996 917
        $this->changeTrackingPolicy = $policy;
997 917
    }
998
999
    /**
1000
     * Whether the change tracking policy of this class is "deferred explicit".
1001
     */
1002 64
    public function isChangeTrackingDeferredExplicit() : bool
1003
    {
1004 64
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT;
1005
    }
1006
1007
    /**
1008
     * Whether the change tracking policy of this class is "deferred implicit".
1009
     */
1010 607
    public function isChangeTrackingDeferredImplicit() : bool
1011
    {
1012 607
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT;
1013
    }
1014
1015
    /**
1016
     * Whether the change tracking policy of this class is "notify".
1017
     */
1018 335
    public function isChangeTrackingNotify() : bool
1019
    {
1020 335
        return $this->changeTrackingPolicy === self::CHANGETRACKING_NOTIFY;
1021
    }
1022
1023
    /**
1024
     * Gets the ReflectionProperties of the mapped class.
1025
     */
1026 100
    public function getReflectionProperties() : array
1027
    {
1028 100
        return $this->reflFields;
1029
    }
1030
1031
    /**
1032
     * Gets a ReflectionProperty for a specific field of the mapped class.
1033
     */
1034
    public function getReflectionProperty(string $name) : ReflectionProperty
1035
    {
1036
        return $this->reflFields[$name];
1037
    }
1038
1039
    /**
1040
     * {@inheritDoc}
1041
     */
1042 1440
    public function getName() : string
1043
    {
1044 1440
        return $this->name;
1045
    }
1046
1047
    /**
1048
     * Returns the database this Document is mapped to.
1049
     */
1050 1361
    public function getDatabase() : ?string
1051
    {
1052 1361
        return $this->db;
1053
    }
1054
1055
    /**
1056
     * Set the database this Document is mapped to.
1057
     */
1058 111
    public function setDatabase(?string $db) : void
1059
    {
1060 111
        $this->db = $db;
1061 111
    }
1062
1063
    /**
1064
     * Get the collection this Document is mapped to.
1065
     */
1066 1353
    public function getCollection() : string
1067
    {
1068 1353
        return $this->collection;
1069
    }
1070
1071
    /**
1072
     * Sets the collection this Document is mapped to.
1073
     *
1074
     * @param array|string $name
1075
     *
1076
     * @throws InvalidArgumentException
1077
     */
1078 1551
    public function setCollection($name) : void
1079
    {
1080 1551
        if (is_array($name)) {
1081 1
            if (! isset($name['name'])) {
1082
                throw new InvalidArgumentException('A name key is required when passing an array to setCollection()');
1083
            }
1084 1
            $this->collectionCapped = $name['capped'] ?? false;
1085 1
            $this->collectionSize   = $name['size'] ?? 0;
1086 1
            $this->collectionMax    = $name['max'] ?? 0;
1087 1
            $this->collection       = $name['name'];
1088
        } else {
1089 1551
            $this->collection = $name;
1090
        }
1091 1551
    }
1092
1093 18
    public function getBucketName() : ?string
1094
    {
1095 18
        return $this->bucketName;
1096
    }
1097
1098 78
    public function setBucketName(string $bucketName) : void
1099
    {
1100 78
        $this->bucketName = $bucketName;
1101 78
        $this->setCollection($bucketName . '.files');
1102 78
    }
1103
1104 9
    public function getChunkSizeBytes() : ?int
1105
    {
1106 9
        return $this->chunkSizeBytes;
1107
    }
1108
1109 78
    public function setChunkSizeBytes(int $chunkSizeBytes) : void
1110
    {
1111 78
        $this->chunkSizeBytes = $chunkSizeBytes;
1112 78
    }
1113
1114
    /**
1115
     * Get whether or not the documents collection is capped.
1116
     */
1117 5
    public function getCollectionCapped() : bool
1118
    {
1119 5
        return $this->collectionCapped;
1120
    }
1121
1122
    /**
1123
     * Set whether or not the documents collection is capped.
1124
     */
1125 1
    public function setCollectionCapped(bool $bool) : void
1126
    {
1127 1
        $this->collectionCapped = $bool;
1128 1
    }
1129
1130
    /**
1131
     * Get the collection size
1132
     */
1133 5
    public function getCollectionSize() : ?int
1134
    {
1135 5
        return $this->collectionSize;
1136
    }
1137
1138
    /**
1139
     * Set the collection size.
1140
     */
1141 1
    public function setCollectionSize(int $size) : void
1142
    {
1143 1
        $this->collectionSize = $size;
1144 1
    }
1145
1146
    /**
1147
     * Get the collection max.
1148
     */
1149 5
    public function getCollectionMax() : ?int
1150
    {
1151 5
        return $this->collectionMax;
1152
    }
1153
1154
    /**
1155
     * Set the collection max.
1156
     */
1157 1
    public function setCollectionMax(int $max) : void
1158
    {
1159 1
        $this->collectionMax = $max;
1160 1
    }
1161
1162
    /**
1163
     * Returns TRUE if this Document is mapped to a collection FALSE otherwise.
1164
     */
1165
    public function isMappedToCollection() : bool
1166
    {
1167
        return $this->collection ? true : false;
1168
    }
1169
1170
    /**
1171
     * Validates the storage strategy of a mapping for consistency
1172
     *
1173
     * @throws MappingException
1174
     */
1175 1458
    private function applyStorageStrategy(array &$mapping) : void
1176
    {
1177 1458
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1178 1440
            return;
1179
        }
1180
1181
        switch (true) {
1182 1424
            case $mapping['type'] === 'int':
1183 1423
            case $mapping['type'] === 'float':
1184 876
                $defaultStrategy   = self::STORAGE_STRATEGY_SET;
1185 876
                $allowedStrategies = [self::STORAGE_STRATEGY_SET, self::STORAGE_STRATEGY_INCREMENT];
1186 876
                break;
1187
1188 1423
            case $mapping['type'] === 'many':
1189 1125
                $defaultStrategy   = CollectionHelper::DEFAULT_STRATEGY;
1190
                $allowedStrategies = [
1191 1125
                    self::STORAGE_STRATEGY_PUSH_ALL,
1192 1125
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1193 1125
                    self::STORAGE_STRATEGY_SET,
1194 1125
                    self::STORAGE_STRATEGY_SET_ARRAY,
1195 1125
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1196 1125
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1197
                ];
1198 1125
                break;
1199
1200
            default:
1201 1410
                $defaultStrategy   = self::STORAGE_STRATEGY_SET;
1202 1410
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1203
        }
1204
1205 1424
        if (! isset($mapping['strategy'])) {
1206 1415
            $mapping['strategy'] = $defaultStrategy;
1207
        }
1208
1209 1424
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1210
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1211
        }
1212
1213 1424
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1214 1424
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1215 1
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1216
        }
1217 1423
    }
1218
1219
    /**
1220
     * Map a single embedded document.
1221
     */
1222 6
    public function mapOneEmbedded(array $mapping) : void
1223
    {
1224 6
        $mapping['embedded'] = true;
1225 6
        $mapping['type']     = 'one';
1226 6
        $this->mapField($mapping);
1227 5
    }
1228
1229
    /**
1230
     * Map a collection of embedded documents.
1231
     */
1232 5
    public function mapManyEmbedded(array $mapping) : void
1233
    {
1234 5
        $mapping['embedded'] = true;
1235 5
        $mapping['type']     = 'many';
1236 5
        $this->mapField($mapping);
1237 5
    }
1238
1239
    /**
1240
     * Map a single document reference.
1241
     */
1242 2
    public function mapOneReference(array $mapping) : void
1243
    {
1244 2
        $mapping['reference'] = true;
1245 2
        $mapping['type']      = 'one';
1246 2
        $this->mapField($mapping);
1247 2
    }
1248
1249
    /**
1250
     * Map a collection of document references.
1251
     */
1252 1
    public function mapManyReference(array $mapping) : void
1253
    {
1254 1
        $mapping['reference'] = true;
1255 1
        $mapping['type']      = 'many';
1256 1
        $this->mapField($mapping);
1257 1
    }
1258
1259
    /**
1260
     * INTERNAL:
1261
     * Adds a field mapping without completing/validating it.
1262
     * This is mainly used to add inherited field mappings to derived classes.
1263
     */
1264 138
    public function addInheritedFieldMapping(array $fieldMapping) : void
1265
    {
1266 138
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1267
1268 138
        if (! isset($fieldMapping['association'])) {
1269 138
            return;
1270
        }
1271
1272 89
        $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1273 89
    }
1274
1275
    /**
1276
     * INTERNAL:
1277
     * Adds an association mapping without completing/validating it.
1278
     * This is mainly used to add inherited association mappings to derived classes.
1279
     *
1280
     * @throws MappingException
1281
     */
1282 90
    public function addInheritedAssociationMapping(array $mapping) : void
1283
    {
1284 90
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1285 90
    }
1286
1287
    /**
1288
     * Checks whether the class has a mapped association with the given field name.
1289
     */
1290 32
    public function hasReference(string $fieldName) : bool
1291
    {
1292 32
        return isset($this->fieldMappings[$fieldName]['reference']);
1293
    }
1294
1295
    /**
1296
     * Checks whether the class has a mapped embed with the given field name.
1297
     */
1298 5
    public function hasEmbed(string $fieldName) : bool
1299
    {
1300 5
        return isset($this->fieldMappings[$fieldName]['embedded']);
1301
    }
1302
1303
    /**
1304
     * {@inheritDoc}
1305
     *
1306
     * Checks whether the class has a mapped association (embed or reference) with the given field name.
1307
     */
1308 7
    public function hasAssociation($fieldName) : bool
1309
    {
1310 7
        return $this->hasReference($fieldName) || $this->hasEmbed($fieldName);
1311
    }
1312
1313
    /**
1314
     * {@inheritDoc}
1315
     *
1316
     * Checks whether the class has a mapped reference or embed for the specified field and
1317
     * is a single valued association.
1318
     */
1319
    public function isSingleValuedAssociation($fieldName) : bool
1320
    {
1321
        return $this->isSingleValuedReference($fieldName) || $this->isSingleValuedEmbed($fieldName);
1322
    }
1323
1324
    /**
1325
     * {@inheritDoc}
1326
     *
1327
     * Checks whether the class has a mapped reference or embed for the specified field and
1328
     * is a collection valued association.
1329
     */
1330
    public function isCollectionValuedAssociation($fieldName) : bool
1331
    {
1332
        return $this->isCollectionValuedReference($fieldName) || $this->isCollectionValuedEmbed($fieldName);
1333
    }
1334
1335
    /**
1336
     * Checks whether the class has a mapped association for the specified field
1337
     * and if yes, checks whether it is a single-valued association (to-one).
1338
     */
1339
    public function isSingleValuedReference(string $fieldName) : bool
1340
    {
1341
        return isset($this->fieldMappings[$fieldName]['association']) &&
1342
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_ONE;
1343
    }
1344
1345
    /**
1346
     * Checks whether the class has a mapped association for the specified field
1347
     * and if yes, checks whether it is a collection-valued association (to-many).
1348
     */
1349
    public function isCollectionValuedReference(string $fieldName) : bool
1350
    {
1351
        return isset($this->fieldMappings[$fieldName]['association']) &&
1352
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_MANY;
1353
    }
1354
1355
    /**
1356
     * Checks whether the class has a mapped embedded document for the specified field
1357
     * and if yes, checks whether it is a single-valued association (to-one).
1358
     */
1359
    public function isSingleValuedEmbed(string $fieldName) : bool
1360
    {
1361
        return isset($this->fieldMappings[$fieldName]['association']) &&
1362
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_ONE;
1363
    }
1364
1365
    /**
1366
     * Checks whether the class has a mapped embedded document for the specified field
1367
     * and if yes, checks whether it is a collection-valued association (to-many).
1368
     */
1369
    public function isCollectionValuedEmbed(string $fieldName) : bool
1370
    {
1371
        return isset($this->fieldMappings[$fieldName]['association']) &&
1372
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_MANY;
1373
    }
1374
1375
    /**
1376
     * Sets the ID generator used to generate IDs for instances of this class.
1377
     */
1378 1376
    public function setIdGenerator(AbstractIdGenerator $generator) : void
1379
    {
1380 1376
        $this->idGenerator = $generator;
1381 1376
    }
1382
1383
    /**
1384
     * Casts the identifier to its portable PHP type.
1385
     *
1386
     * @param mixed $id
1387
     *
1388
     * @return mixed $id
1389
     */
1390 636
    public function getPHPIdentifierValue($id)
1391
    {
1392 636
        $idType = $this->fieldMappings[$this->identifier]['type'];
1393 636
        return Type::getType($idType)->convertToPHPValue($id);
1394
    }
1395
1396
    /**
1397
     * Casts the identifier to its database type.
1398
     *
1399
     * @param mixed $id
1400
     *
1401
     * @return mixed $id
1402
     */
1403 700
    public function getDatabaseIdentifierValue($id)
1404
    {
1405 700
        $idType = $this->fieldMappings[$this->identifier]['type'];
1406 700
        return Type::getType($idType)->convertToDatabaseValue($id);
1407
    }
1408
1409
    /**
1410
     * Sets the document identifier of a document.
1411
     *
1412
     * The value will be converted to a PHP type before being set.
1413
     *
1414
     * @param mixed $id
1415
     */
1416 555
    public function setIdentifierValue(object $document, $id) : void
1417
    {
1418 555
        $id = $this->getPHPIdentifierValue($id);
1419 555
        $this->reflFields[$this->identifier]->setValue($document, $id);
1420 555
    }
1421
1422
    /**
1423
     * Gets the document identifier as a PHP type.
1424
     *
1425
     * @return mixed $id
1426
     */
1427 640
    public function getIdentifierValue(object $document)
1428
    {
1429 640
        return $this->reflFields[$this->identifier]->getValue($document);
1430
    }
1431
1432
    /**
1433
     * {@inheritDoc}
1434
     *
1435
     * Since MongoDB only allows exactly one identifier field this is a proxy
1436
     * to {@see getIdentifierValue()} and returns an array with the identifier
1437
     * field as a key.
1438
     */
1439
    public function getIdentifierValues($object) : array
1440
    {
1441
        return [$this->identifier => $this->getIdentifierValue($object)];
1442
    }
1443
1444
    /**
1445
     * Get the document identifier object as a database type.
1446
     *
1447
     * @return mixed $id
1448
     */
1449 31
    public function getIdentifierObject(object $document)
1450
    {
1451 31
        return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));
1452
    }
1453
1454
    /**
1455
     * Sets the specified field to the specified value on the given document.
1456
     *
1457
     * @param mixed $value
1458
     */
1459 8
    public function setFieldValue(object $document, string $field, $value) : void
1460
    {
1461 8
        if ($document instanceof Proxy && ! $document->__isInitialized()) {
1462
            //property changes to an uninitialized proxy will not be tracked or persisted,
1463
            //so the proxy needs to be loaded first.
1464 1
            $document->__load();
1465
        }
1466
1467 8
        $this->reflFields[$field]->setValue($document, $value);
1468 8
    }
1469
1470
    /**
1471
     * Gets the specified field's value off the given document.
1472
     *
1473
     * @return mixed
1474
     */
1475 32
    public function getFieldValue(object $document, string $field)
1476
    {
1477 32
        if ($document instanceof Proxy && $field !== $this->identifier && ! $document->__isInitialized()) {
1478 1
            $document->__load();
1479
        }
1480
1481 32
        return $this->reflFields[$field]->getValue($document);
1482
    }
1483
1484
    /**
1485
     * Gets the mapping of a field.
1486
     *
1487
     * @throws MappingException If the $fieldName is not found in the fieldMappings array.
1488
     */
1489 174
    public function getFieldMapping(string $fieldName) : array
1490
    {
1491 174
        if (! isset($this->fieldMappings[$fieldName])) {
1492 6
            throw MappingException::mappingNotFound($this->name, $fieldName);
1493
        }
1494 172
        return $this->fieldMappings[$fieldName];
1495
    }
1496
1497
    /**
1498
     * Gets mappings of fields holding embedded document(s).
1499
     */
1500 585
    public function getEmbeddedFieldsMappings() : array
1501
    {
1502 585
        return array_filter(
1503 585
            $this->associationMappings,
1504
            static function ($assoc) {
1505 449
                return ! empty($assoc['embedded']);
1506 585
            }
1507
        );
1508
    }
1509
1510
    /**
1511
     * Gets the field mapping by its DB name.
1512
     * E.g. it returns identifier's mapping when called with _id.
1513
     *
1514
     * @throws MappingException
1515
     */
1516 13
    public function getFieldMappingByDbFieldName(string $dbFieldName) : array
1517
    {
1518 13
        foreach ($this->fieldMappings as $mapping) {
1519 13
            if ($mapping['name'] === $dbFieldName) {
1520 13
                return $mapping;
1521
            }
1522
        }
1523
1524 1
        throw MappingException::mappingNotFoundByDbName($this->name, $dbFieldName);
1525
    }
1526
1527
    /**
1528
     * Check if the field is not null.
1529
     */
1530 1
    public function isNullable(string $fieldName) : bool
1531
    {
1532 1
        $mapping = $this->getFieldMapping($fieldName);
1533 1
        if ($mapping !== false) {
1534 1
            return isset($mapping['nullable']) && $mapping['nullable'] === true;
1535
        }
1536
        return false;
1537
    }
1538
1539
    /**
1540
     * Checks whether the document has a discriminator field and value configured.
1541
     */
1542 515
    public function hasDiscriminator() : bool
1543
    {
1544 515
        return isset($this->discriminatorField, $this->discriminatorValue);
1545
    }
1546
1547
    /**
1548
     * Sets the type of Id generator to use for the mapped class.
1549
     */
1550 915
    public function setIdGeneratorType(int $generatorType) : void
1551
    {
1552 915
        $this->generatorType = $generatorType;
1553 915
    }
1554
1555
    /**
1556
     * Sets the Id generator options.
1557
     */
1558
    public function setIdGeneratorOptions(array $generatorOptions) : void
1559
    {
1560
        $this->generatorOptions = $generatorOptions;
1561
    }
1562
1563 604
    public function isInheritanceTypeNone() : bool
1564
    {
1565 604
        return $this->inheritanceType === self::INHERITANCE_TYPE_NONE;
1566
    }
1567
1568
    /**
1569
     * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.
1570
     */
1571 914
    public function isInheritanceTypeSingleCollection() : bool
1572
    {
1573 914
        return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION;
1574
    }
1575
1576
    /**
1577
     * Checks whether the mapped class uses the COLLECTION_PER_CLASS inheritance mapping strategy.
1578
     */
1579
    public function isInheritanceTypeCollectionPerClass() : bool
1580
    {
1581
        return $this->inheritanceType === self::INHERITANCE_TYPE_COLLECTION_PER_CLASS;
1582
    }
1583
1584
    /**
1585
     * Sets the mapped subclasses of this class.
1586
     *
1587
     * @param string[] $subclasses The names of all mapped subclasses.
1588
     */
1589 2
    public function setSubclasses(array $subclasses) : void
1590
    {
1591 2
        foreach ($subclasses as $subclass) {
1592 2
            $this->subClasses[] = $subclass;
1593
        }
1594 2
    }
1595
1596
    /**
1597
     * Sets the parent class names.
1598
     * Assumes that the class names in the passed array are in the order:
1599
     * directParent -> directParentParent -> directParentParentParent ... -> root.
1600
     *
1601
     * @param string[] $classNames
1602
     */
1603 1431
    public function setParentClasses(array $classNames) : void
1604
    {
1605 1431
        $this->parentClasses = $classNames;
1606
1607 1431
        if (count($classNames) <= 0) {
1608 1430
            return;
1609
        }
1610
1611 122
        $this->rootDocumentName = array_pop($classNames);
1612 122
    }
1613
1614
    /**
1615
     * Checks whether the class will generate a new \MongoDB\BSON\ObjectId instance for us.
1616
     */
1617
    public function isIdGeneratorAuto() : bool
1618
    {
1619
        return $this->generatorType === self::GENERATOR_TYPE_AUTO;
1620
    }
1621
1622
    /**
1623
     * Checks whether the class will use a collection to generate incremented identifiers.
1624
     */
1625
    public function isIdGeneratorIncrement() : bool
1626
    {
1627
        return $this->generatorType === self::GENERATOR_TYPE_INCREMENT;
1628
    }
1629
1630
    /**
1631
     * Checks whether the class will generate a uuid id.
1632
     */
1633
    public function isIdGeneratorUuid() : bool
1634
    {
1635
        return $this->generatorType === self::GENERATOR_TYPE_UUID;
1636
    }
1637
1638
    /**
1639
     * Checks whether the class uses no id generator.
1640
     */
1641
    public function isIdGeneratorNone() : bool
1642
    {
1643
        return $this->generatorType === self::GENERATOR_TYPE_NONE;
1644
    }
1645
1646
    /**
1647
     * Sets the version field mapping used for versioning. Sets the default
1648
     * value to use depending on the column type.
1649
     *
1650
     * @throws LockException
1651
     */
1652 106
    public function setVersionMapping(array &$mapping) : void
1653
    {
1654 106
        if ($mapping['type'] !== 'int' && $mapping['type'] !== 'date') {
1655 1
            throw LockException::invalidVersionFieldType($mapping['type']);
1656
        }
1657
1658 105
        $this->isVersioned  = true;
1659 105
        $this->versionField = $mapping['fieldName'];
1660 105
    }
1661
1662
    /**
1663
     * Sets whether this class is to be versioned for optimistic locking.
1664
     */
1665 915
    public function setVersioned(bool $bool) : void
1666
    {
1667 915
        $this->isVersioned = $bool;
1668 915
    }
1669
1670
    /**
1671
     * Sets the name of the field that is to be used for versioning if this class is
1672
     * versioned for optimistic locking.
1673
     */
1674 915
    public function setVersionField(?string $versionField) : void
1675
    {
1676 915
        $this->versionField = $versionField;
1677 915
    }
1678
1679
    /**
1680
     * Sets the version field mapping used for versioning. Sets the default
1681
     * value to use depending on the column type.
1682
     *
1683
     * @throws LockException
1684
     */
1685 22
    public function setLockMapping(array &$mapping) : void
1686
    {
1687 22
        if ($mapping['type'] !== 'int') {
1688 1
            throw LockException::invalidLockFieldType($mapping['type']);
1689
        }
1690
1691 21
        $this->isLockable = true;
1692 21
        $this->lockField  = $mapping['fieldName'];
1693 21
    }
1694
1695
    /**
1696
     * Sets whether this class is to allow pessimistic locking.
1697
     */
1698
    public function setLockable(bool $bool) : void
1699
    {
1700
        $this->isLockable = $bool;
1701
    }
1702
1703
    /**
1704
     * Sets the name of the field that is to be used for storing whether a document
1705
     * is currently locked or not.
1706
     */
1707
    public function setLockField(string $lockField) : void
1708
    {
1709
        $this->lockField = $lockField;
1710
    }
1711
1712
    /**
1713
     * Marks this class as read only, no change tracking is applied to it.
1714
     */
1715 5
    public function markReadOnly() : void
1716
    {
1717 5
        $this->isReadOnly = true;
1718 5
    }
1719
1720
    /**
1721
     * {@inheritDoc}
1722
     */
1723
    public function getFieldNames() : array
1724
    {
1725
        return array_keys($this->fieldMappings);
1726
    }
1727
1728
    /**
1729
     * {@inheritDoc}
1730
     */
1731
    public function getAssociationNames() : array
1732
    {
1733
        return array_keys($this->associationMappings);
1734
    }
1735
1736
    /**
1737
     * {@inheritDoc}
1738
     */
1739 23
    public function getTypeOfField($fieldName) : ?string
1740
    {
1741 23
        return isset($this->fieldMappings[$fieldName]) ?
1742 23
            $this->fieldMappings[$fieldName]['type'] : null;
1743
    }
1744
1745
    /**
1746
     * {@inheritDoc}
1747
     */
1748 4
    public function getAssociationTargetClass($assocName) : string
1749
    {
1750 4
        if (! isset($this->associationMappings[$assocName])) {
1751 2
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1752
        }
1753
1754 2
        return $this->associationMappings[$assocName]['targetDocument'];
1755
    }
1756
1757
    /**
1758
     * Retrieve the collectionClass associated with an association
1759
     */
1760 1
    public function getAssociationCollectionClass(string $assocName) : string
1761
    {
1762 1
        if (! isset($this->associationMappings[$assocName])) {
1763
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1764
        }
1765
1766 1
        if (! array_key_exists('collectionClass', $this->associationMappings[$assocName])) {
1767
            throw new InvalidArgumentException("collectionClass can only be applied to 'embedMany' and 'referenceMany' associations.");
1768
        }
1769
1770 1
        return $this->associationMappings[$assocName]['collectionClass'];
1771
    }
1772
1773
    /**
1774
     * {@inheritDoc}
1775
     */
1776
    public function isAssociationInverseSide($fieldName) : bool
1777
    {
1778
        throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1779
    }
1780
1781
    /**
1782
     * {@inheritDoc}
1783
     */
1784
    public function getAssociationMappedByTargetField($fieldName)
1785
    {
1786
        throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1787
    }
1788
1789
    /**
1790
     * Map a field.
1791
     *
1792
     * @throws MappingException
1793
     */
1794 1474
    public function mapField(array $mapping) : array
1795
    {
1796 1474
        if (! isset($mapping['fieldName']) && isset($mapping['name'])) {
1797 6
            $mapping['fieldName'] = $mapping['name'];
1798
        }
1799 1474
        if (! isset($mapping['fieldName'])) {
1800
            throw MappingException::missingFieldName($this->name);
1801
        }
1802 1474
        if (! isset($mapping['name'])) {
1803 1472
            $mapping['name'] = $mapping['fieldName'];
1804
        }
1805 1474
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1806 1
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, $mapping['name']);
1807
        }
1808 1473
        if ($this->discriminatorField !== null && $this->discriminatorField === $mapping['name']) {
1809 1
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1810
        }
1811 1472
        if (isset($mapping['collectionClass'])) {
1812 73
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1813
        }
1814 1472
        if (! empty($mapping['collectionClass'])) {
1815 73
            $rColl = new ReflectionClass($mapping['collectionClass']);
1816 73
            if (! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1817 1
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1818
            }
1819
        }
1820
1821 1471
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1822 1
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1823
        }
1824
1825 1470
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : [];
1826
1827 1470
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
1828 1164
            $cascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1829
        }
1830
1831 1470
        if (isset($mapping['embedded'])) {
1832 1125
            unset($mapping['cascade']);
1833 1465
        } elseif (isset($mapping['cascade'])) {
1834 943
            $mapping['cascade'] = $cascades;
1835
        }
1836
1837 1470
        $mapping['isCascadeRemove']  = in_array('remove', $cascades);
1838 1470
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1839 1470
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1840 1470
        $mapping['isCascadeMerge']   = in_array('merge', $cascades);
1841 1470
        $mapping['isCascadeDetach']  = in_array('detach', $cascades);
1842
1843 1470
        if (isset($mapping['id']) && $mapping['id'] === true) {
1844 1438
            $mapping['name']  = '_id';
1845 1438
            $this->identifier = $mapping['fieldName'];
1846 1438
            if (isset($mapping['strategy'])) {
1847 1432
                $this->generatorType = constant(self::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
1848
            }
1849 1438
            $this->generatorOptions = $mapping['options'] ?? [];
1850 1438
            switch ($this->generatorType) {
1851 1438
                case self::GENERATOR_TYPE_AUTO:
1852 1366
                    $mapping['type'] = 'id';
1853 1366
                    break;
1854
                default:
1855 165
                    if (! empty($this->generatorOptions['type'])) {
1856 56
                        $mapping['type'] = $this->generatorOptions['type'];
1857 109
                    } elseif (empty($mapping['type'])) {
1858 97
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
1859
                    }
1860
            }
1861 1438
            unset($this->generatorOptions['type']);
1862
        }
1863
1864 1470
        if (! isset($mapping['nullable'])) {
1865 39
            $mapping['nullable'] = false;
1866
        }
1867
1868 1470
        if (isset($mapping['reference'])
1869 1470
            && isset($mapping['storeAs'])
1870 1470
            && $mapping['storeAs'] === self::REFERENCE_STORE_AS_ID
1871 1470
            && ! isset($mapping['targetDocument'])
1872
        ) {
1873 3
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
1874
        }
1875
1876 1467
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
1877 1467
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
1878 4
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
1879
        }
1880
1881 1463
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
1882 1
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
1883
        }
1884
1885 1462
        if (isset($mapping['repositoryMethod']) && ! (empty($mapping['skip']) && empty($mapping['limit']) && empty($mapping['sort']))) {
1886 3
            throw MappingException::repositoryMethodCanNotBeCombinedWithSkipLimitAndSort($this->name, $mapping['fieldName']);
1887
        }
1888
1889 1459
        if (isset($mapping['reference']) && $mapping['type'] === 'one') {
1890 1048
            $mapping['association'] = self::REFERENCE_ONE;
1891
        }
1892 1459
        if (isset($mapping['reference']) && $mapping['type'] === 'many') {
1893 995
            $mapping['association'] = self::REFERENCE_MANY;
1894
        }
1895 1459
        if (isset($mapping['embedded']) && $mapping['type'] === 'one') {
1896 987
            $mapping['association'] = self::EMBED_ONE;
1897
        }
1898 1459
        if (isset($mapping['embedded']) && $mapping['type'] === 'many') {
1899 1023
            $mapping['association'] = self::EMBED_MANY;
1900
        }
1901
1902 1459
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
1903 144
            $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
1904
        }
1905
1906 1459
        if (isset($mapping['targetDocument']) && ! class_exists($mapping['targetDocument']) && ! interface_exists($mapping['targetDocument'])) {
1907 5
            $fullClassName = $this->reflClass->getNamespaceName() . '\\' . $mapping['targetDocument'];
1908 5
            if (! class_exists($fullClassName) && ! interface_exists($fullClassName)) {
1909
                throw MappingException::nonExistingClass($mapping['targetDocument']);
1910
            }
1911
1912 5
            $mapping['targetDocument'] = $fullClassName;
1913
        }
1914
1915 1459
        if (isset($mapping['discriminatorMap'])) {
1916 134
            foreach ($mapping['discriminatorMap'] as $value => $targetDocument) {
1917 134
                if (class_exists($targetDocument) || interface_exists($targetDocument)) {
1918 119
                    continue;
1919
                }
1920 15
                $fullClassName = $this->reflClass->getNamespaceName() . '\\' . $targetDocument;
1921 15
                if (! class_exists($fullClassName) && ! interface_exists($fullClassName)) {
1922
                    throw MappingException::nonExistingClass($fullClassName);
1923
                }
1924 15
                $mapping['discriminatorMap'][$value] = $fullClassName;
1925
            }
1926
        }
1927
1928 1459
        if (isset($mapping['version'])) {
1929 106
            $mapping['notSaved'] = true;
1930 106
            $this->setVersionMapping($mapping);
1931
        }
1932 1459
        if (isset($mapping['lock'])) {
1933 22
            $mapping['notSaved'] = true;
1934 22
            $this->setLockMapping($mapping);
1935
        }
1936 1459
        $mapping['isOwningSide']  = true;
1937 1459
        $mapping['isInverseSide'] = false;
1938 1459
        if (isset($mapping['reference'])) {
1939 1116
            if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
1940 104
                $mapping['isOwningSide']  = true;
1941 104
                $mapping['isInverseSide'] = false;
1942
            }
1943 1116
            if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
1944 842
                $mapping['isInverseSide'] = true;
1945 842
                $mapping['isOwningSide']  = false;
1946
            }
1947 1116
            if (isset($mapping['repositoryMethod'])) {
1948 78
                $mapping['isInverseSide'] = true;
1949 78
                $mapping['isOwningSide']  = false;
1950
            }
1951 1116
            if (! isset($mapping['orphanRemoval'])) {
1952 1096
                $mapping['orphanRemoval'] = false;
1953
            }
1954
        }
1955
1956 1459
        if (! empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || ! $mapping['isInverseSide'])) {
1957
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
1958
        }
1959
1960 1459
        if ($this->isFile && ! $this->isAllowedGridFSField($mapping['name'])) {
1961 1
            throw MappingException::fieldNotAllowedForGridFS($this->name, $mapping['fieldName']);
1962
        }
1963
1964 1458
        $this->applyStorageStrategy($mapping);
1965
1966 1457
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
1967 1457
        if (isset($mapping['association'])) {
1968 1265
            $this->associationMappings[$mapping['fieldName']] = $mapping;
1969
        }
1970
1971 1457
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
1972 1456
        $reflProp->setAccessible(true);
1973 1456
        $this->reflFields[$mapping['fieldName']] = $reflProp;
1974
1975 1456
        return $mapping;
1976
    }
1977
1978
    /**
1979
     * Determines which fields get serialized.
1980
     *
1981
     * It is only serialized what is necessary for best unserialization performance.
1982
     * That means any metadata properties that are not set or empty or simply have
1983
     * their default value are NOT serialized.
1984
     *
1985
     * Parts that are also NOT serialized because they can not be properly unserialized:
1986
     *      - reflClass (ReflectionClass)
1987
     *      - reflFields (ReflectionProperty array)
1988
     *
1989
     * @return array The names of all the fields that should be serialized.
1990
     */
1991 6
    public function __sleep()
1992
    {
1993
        // This metadata is always serialized/cached.
1994
        $serialized = [
1995 6
            'fieldMappings',
1996
            'associationMappings',
1997
            'identifier',
1998
            'name',
1999
            'db',
2000
            'collection',
2001
            'readPreference',
2002
            'readPreferenceTags',
2003
            'writeConcern',
2004
            'rootDocumentName',
2005
            'generatorType',
2006
            'generatorOptions',
2007
            'idGenerator',
2008
            'indexes',
2009
            'shardKey',
2010
        ];
2011
2012
        // The rest of the metadata is only serialized if necessary.
2013 6
        if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {
2014
            $serialized[] = 'changeTrackingPolicy';
2015
        }
2016
2017 6
        if ($this->customRepositoryClassName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customRepositoryClassName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2018 1
            $serialized[] = 'customRepositoryClassName';
2019
        }
2020
2021 6
        if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE || $this->discriminatorField !== null) {
2022 4
            $serialized[] = 'inheritanceType';
2023 4
            $serialized[] = 'discriminatorField';
2024 4
            $serialized[] = 'discriminatorValue';
2025 4
            $serialized[] = 'discriminatorMap';
2026 4
            $serialized[] = 'defaultDiscriminatorValue';
2027 4
            $serialized[] = 'parentClasses';
2028 4
            $serialized[] = 'subClasses';
2029
        }
2030
2031 6
        if ($this->isMappedSuperclass) {
2032 1
            $serialized[] = 'isMappedSuperclass';
2033
        }
2034
2035 6
        if ($this->isEmbeddedDocument) {
2036 1
            $serialized[] = 'isEmbeddedDocument';
2037
        }
2038
2039 6
        if ($this->isQueryResultDocument) {
2040
            $serialized[] = 'isQueryResultDocument';
2041
        }
2042
2043 6
        if ($this->isFile) {
2044
            $serialized[] = 'isFile';
2045
            $serialized[] = 'bucketName';
2046
            $serialized[] = 'chunkSizeBytes';
2047
        }
2048
2049 6
        if ($this->isVersioned) {
2050
            $serialized[] = 'isVersioned';
2051
            $serialized[] = 'versionField';
2052
        }
2053
2054 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...
2055
            $serialized[] = 'lifecycleCallbacks';
2056
        }
2057
2058 6
        if ($this->collectionCapped) {
2059 1
            $serialized[] = 'collectionCapped';
2060 1
            $serialized[] = 'collectionSize';
2061 1
            $serialized[] = 'collectionMax';
2062
        }
2063
2064 6
        if ($this->isReadOnly) {
2065
            $serialized[] = 'isReadOnly';
2066
        }
2067
2068 6
        return $serialized;
2069
    }
2070
2071
    /**
2072
     * Restores some state that can not be serialized/unserialized.
2073
     */
2074 6
    public function __wakeup()
2075
    {
2076
        // Restore ReflectionClass and properties
2077 6
        $this->reflClass    = new ReflectionClass($this->name);
2078 6
        $this->instantiator = $this->instantiator ?: new Instantiator();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->instantiator ?: n...antiator\Instantiator() can also be of type object<Doctrine\Instantiator\Instantiator>. However, the property $instantiator is declared as type object<Doctrine\Instanti...antiatorInterface>|null. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2079
2080 6
        foreach ($this->fieldMappings as $field => $mapping) {
2081 3
            if (isset($mapping['declared'])) {
2082 1
                $reflField = new ReflectionProperty($mapping['declared'], $field);
2083
            } else {
2084 3
                $reflField = $this->reflClass->getProperty($field);
2085
            }
2086 3
            $reflField->setAccessible(true);
2087 3
            $this->reflFields[$field] = $reflField;
2088
        }
2089 6
    }
2090
2091
    /**
2092
     * Creates a new instance of the mapped class, without invoking the constructor.
2093
     */
2094 368
    public function newInstance() : object
2095
    {
2096 368
        return $this->instantiator->instantiate($this->name);
2097
    }
2098
2099 80
    private function isAllowedGridFSField(string $name) : bool
2100
    {
2101 80
        return in_array($name, self::ALLOWED_GRIDFS_FIELDS, true);
2102
    }
2103
}
2104