Completed
Pull Request — master (#2025)
by Andreas
17:58
created

ClassMetadata::setLifecycleCallbacks()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 3
cts 3
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB\Mapping;
6
7
use 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\Types\Type;
14
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
15
use InvalidArgumentException;
16
use LogicException;
17
use ProxyManager\Proxy\GhostObjectInterface;
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 class_exists;
26
use function constant;
27
use function count;
28
use function get_class;
29
use function in_array;
30
use function is_array;
31
use function is_string;
32
use function is_subclass_of;
33
use function ltrim;
34
use function sprintf;
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
class ClassMetadata implements BaseClassMetadata
53
{
54
    /* The Id generator types. */
55
    /**
56
     * AUTO means Doctrine will automatically create a new \MongoDB\BSON\ObjectId instance for us.
57
     */
58
    public const GENERATOR_TYPE_AUTO = 1;
59
60
    /**
61
     * INCREMENT means a separate collection is used for maintaining and incrementing id generation.
62
     * Offers full portability.
63
     */
64
    public const GENERATOR_TYPE_INCREMENT = 2;
65
66
    /**
67
     * UUID means Doctrine will generate a uuid for us.
68
     */
69
    public const GENERATOR_TYPE_UUID = 3;
70
71
    /**
72
     * ALNUM means Doctrine will generate Alpha-numeric string identifiers, using the INCREMENT
73
     * generator to ensure identifier uniqueness
74
     */
75
    public const GENERATOR_TYPE_ALNUM = 4;
76
77
    /**
78
     * CUSTOM means Doctrine expect a class parameter. It will then try to initiate that class
79
     * and pass other options to the generator. It will throw an Exception if the class
80
     * does not exist or if an option was passed for that there is not setter in the new
81
     * generator class.
82
     *
83
     * The class  will have to be a subtype of AbstractIdGenerator.
84
     */
85
    public const GENERATOR_TYPE_CUSTOM = 5;
86
87
    /**
88
     * NONE means Doctrine will not generate any id for us and you are responsible for manually
89
     * assigning an id.
90
     */
91
    public const GENERATOR_TYPE_NONE = 6;
92
93
    /**
94
     * Default discriminator field name.
95
     *
96
     * This is used for associations value for associations where a that do not define a "targetDocument" or
97
     * "discriminatorField" option in their mapping.
98
     */
99
    public const DEFAULT_DISCRIMINATOR_FIELD = '_doctrine_class_name';
100
101
    public const REFERENCE_ONE  = 1;
102
    public const REFERENCE_MANY = 2;
103
    public const EMBED_ONE      = 3;
104
    public const EMBED_MANY     = 4;
105
    public const MANY           = 'many';
106
    public const ONE            = 'one';
107
108
    /**
109
     * The types of storeAs references
110
     */
111
    public const REFERENCE_STORE_AS_ID             = 'id';
112
    public const REFERENCE_STORE_AS_DB_REF         = 'dbRef';
113
    public const REFERENCE_STORE_AS_DB_REF_WITH_DB = 'dbRefWithDb';
114
    public const REFERENCE_STORE_AS_REF            = 'ref';
115
116
    /* The inheritance mapping types */
117
    /**
118
     * NONE means the class does not participate in an inheritance hierarchy
119
     * and therefore does not need an inheritance mapping type.
120
     */
121
    public const INHERITANCE_TYPE_NONE = 1;
122
123
    /**
124
     * SINGLE_COLLECTION means the class will be persisted according to the rules of
125
     * <tt>Single Collection Inheritance</tt>.
126
     */
127
    public const INHERITANCE_TYPE_SINGLE_COLLECTION = 2;
128
129
    /**
130
     * COLLECTION_PER_CLASS means the class will be persisted according to the rules
131
     * of <tt>Concrete Collection Inheritance</tt>.
132
     */
133
    public const INHERITANCE_TYPE_COLLECTION_PER_CLASS = 3;
134
135
    /**
136
     * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time
137
     * by doing a property-by-property comparison with the original data. This will
138
     * be done for all entities that are in MANAGED state at commit-time.
139
     *
140
     * This is the default change tracking policy.
141
     */
142
    public const CHANGETRACKING_DEFERRED_IMPLICIT = 1;
143
144
    /**
145
     * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time
146
     * by doing a property-by-property comparison with the original data. This will
147
     * be done only for entities that were explicitly saved (through persist() or a cascade).
148
     */
149
    public const CHANGETRACKING_DEFERRED_EXPLICIT = 2;
150
151
    /**
152
     * NOTIFY means that Doctrine relies on the entities sending out notifications
153
     * when their properties change. Such entity classes must implement
154
     * the <tt>NotifyPropertyChanged</tt> interface.
155
     */
156
    public const CHANGETRACKING_NOTIFY = 3;
157
158
    /**
159
     * SET means that fields will be written to the database using a $set operator
160
     */
161
    public const STORAGE_STRATEGY_SET = 'set';
162
163
    /**
164
     * INCREMENT means that fields will be written to the database by calculating
165
     * the difference and using the $inc operator
166
     */
167
    public const STORAGE_STRATEGY_INCREMENT = 'increment';
168
169
    public const STORAGE_STRATEGY_PUSH_ALL         = 'pushAll';
170
    public const STORAGE_STRATEGY_ADD_TO_SET       = 'addToSet';
171
    public const STORAGE_STRATEGY_ATOMIC_SET       = 'atomicSet';
172
    public const STORAGE_STRATEGY_ATOMIC_SET_ARRAY = 'atomicSetArray';
173
    public const STORAGE_STRATEGY_SET_ARRAY        = 'setArray';
174
175
    private const ALLOWED_GRIDFS_FIELDS = ['_id', 'chunkSize', 'filename', 'length', 'metadata', 'uploadDate'];
176
177
    /**
178
     * READ-ONLY: The name of the mongo database the document is mapped to.
179
     *
180
     * @var string|null
181
     */
182
    public $db;
183
184
    /**
185
     * READ-ONLY: The name of the mongo collection the document is mapped to.
186
     *
187
     * @var string
188
     */
189
    public $collection;
190
191
    /**
192
     * READ-ONLY: The name of the GridFS bucket the document is mapped to.
193
     *
194
     * @var string
195
     */
196
    public $bucketName = 'fs';
197
198
    /**
199
     * READ-ONLY: If the collection should be a fixed size.
200
     *
201
     * @var bool
202
     */
203
    public $collectionCapped = false;
204
205
    /**
206
     * READ-ONLY: If the collection is fixed size, its size in bytes.
207
     *
208
     * @var int|null
209
     */
210
    public $collectionSize;
211
212
    /**
213
     * READ-ONLY: If the collection is fixed size, the maximum number of elements to store in the collection.
214
     *
215
     * @var int|null
216
     */
217
    public $collectionMax;
218
219
    /**
220
     * READ-ONLY Describes how MongoDB clients route read operations to the members of a replica set.
221
     *
222
     * @var string|null
223
     */
224
    public $readPreference;
225
226
    /**
227
     * READ-ONLY Associated with readPreference Allows to specify criteria so that your application can target read
228
     * operations to specific members, based on custom parameters.
229
     *
230
     * @var string[][]
231
     */
232
    public $readPreferenceTags = [];
233
234
    /**
235
     * READ-ONLY: Describes the level of acknowledgement requested from MongoDB for write operations.
236
     *
237
     * @var string|int|null
238
     */
239
    public $writeConcern;
240
241
    /**
242
     * READ-ONLY: The field name of the document identifier.
243
     *
244
     * @var string|null
245
     */
246
    public $identifier;
247
248
    /**
249
     * READ-ONLY: The array of indexes for the document collection.
250
     *
251
     * @var array
252
     */
253
    public $indexes = [];
254
255
    /**
256
     * READ-ONLY: Keys and options describing shard key. Only for sharded collections.
257
     *
258
     * @var array<string, array>
259
     */
260
    public $shardKey = [];
261
262
    /**
263
     * READ-ONLY: The name of the document class.
264
     *
265
     * @var string
266
     */
267
    public $name;
268
269
    /**
270
     * READ-ONLY: The name of the document class that is at the root of the mapped document inheritance
271
     * hierarchy. If the document is not part of a mapped inheritance hierarchy this is the same
272
     * as {@link $documentName}.
273
     *
274
     * @var string
275
     */
276
    public $rootDocumentName;
277
278
    /**
279
     * The name of the custom repository class used for the document class.
280
     * (Optional).
281
     *
282
     * @var string|null
283
     */
284
    public $customRepositoryClassName;
285
286
    /**
287
     * READ-ONLY: The names of the parent classes (ancestors).
288
     *
289
     * @var array
290
     */
291
    public $parentClasses = [];
292
293
    /**
294
     * READ-ONLY: The names of all subclasses (descendants).
295
     *
296
     * @var array
297
     */
298
    public $subClasses = [];
299
300
    /**
301
     * The ReflectionProperty instances of the mapped class.
302
     *
303
     * @var ReflectionProperty[]
304
     */
305
    public $reflFields = [];
306
307
    /**
308
     * READ-ONLY: The inheritance mapping type used by the class.
309
     *
310
     * @var int
311
     */
312
    public $inheritanceType = self::INHERITANCE_TYPE_NONE;
313
314
    /**
315
     * READ-ONLY: The Id generator type used by the class.
316
     *
317
     * @var int
318
     */
319
    public $generatorType = self::GENERATOR_TYPE_AUTO;
320
321
    /**
322
     * READ-ONLY: The Id generator options.
323
     *
324
     * @var array
325
     */
326
    public $generatorOptions = [];
327
328
    /**
329
     * READ-ONLY: The ID generator used for generating IDs for this class.
330
     *
331
     * @var AbstractIdGenerator|null
332
     */
333
    public $idGenerator;
334
335
    /**
336
     * READ-ONLY: The field mappings of the class.
337
     * Keys are field names and values are mapping definitions.
338
     *
339
     * The mapping definition array has the following values:
340
     *
341
     * - <b>fieldName</b> (string)
342
     * The name of the field in the Document.
343
     *
344
     * - <b>id</b> (boolean, optional)
345
     * Marks the field as the primary key of the document. Multiple fields of an
346
     * document can have the id attribute, forming a composite key.
347
     *
348
     * @var array
349
     */
350
    public $fieldMappings = [];
351
352
    /**
353
     * READ-ONLY: The association mappings of the class.
354
     * Keys are field names and values are mapping definitions.
355
     *
356
     * @var array
357
     */
358
    public $associationMappings = [];
359
360
    /**
361
     * READ-ONLY: Array of fields to also load with a given method.
362
     *
363
     * @var array
364
     */
365
    public $alsoLoadMethods = [];
366
367
    /**
368
     * READ-ONLY: The registered lifecycle callbacks for documents of this class.
369
     *
370
     * @var array
371
     */
372
    public $lifecycleCallbacks = [];
373
374
    /**
375
     * READ-ONLY: The discriminator value of this class.
376
     *
377
     * <b>This does only apply to the JOINED and SINGLE_COLLECTION inheritance mapping strategies
378
     * where a discriminator field is used.</b>
379
     *
380
     * @see discriminatorField
381
     *
382
     * @var mixed
383
     */
384
    public $discriminatorValue;
385
386
    /**
387
     * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
388
     *
389
     * <b>This does only apply to the SINGLE_COLLECTION inheritance mapping strategy
390
     * where a discriminator field is used.</b>
391
     *
392
     * @see discriminatorField
393
     *
394
     * @var mixed
395
     */
396
    public $discriminatorMap = [];
397
398
    /**
399
     * READ-ONLY: The definition of the discriminator field used in SINGLE_COLLECTION
400
     * inheritance mapping.
401
     *
402
     * @var string|null
403
     */
404
    public $discriminatorField;
405
406
    /**
407
     * READ-ONLY: The default value for discriminatorField in case it's not set in the document
408
     *
409
     * @see discriminatorField
410
     *
411
     * @var string|null
412
     */
413
    public $defaultDiscriminatorValue;
414
415
    /**
416
     * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
417
     *
418
     * @var bool
419
     */
420
    public $isMappedSuperclass = false;
421
422
    /**
423
     * READ-ONLY: Whether this class describes the mapping of a embedded document.
424
     *
425
     * @var bool
426
     */
427
    public $isEmbeddedDocument = false;
428
429
    /**
430
     * READ-ONLY: Whether this class describes the mapping of an aggregation result document.
431
     *
432
     * @var bool
433
     */
434
    public $isQueryResultDocument = false;
435
436
    /**
437
     * READ-ONLY: Whether this class describes the mapping of a gridFS file
438
     *
439
     * @var bool
440
     */
441
    public $isFile = false;
442
443
    /**
444
     * READ-ONLY: The default chunk size in bytes for the file
445
     *
446
     * @var int|null
447
     */
448
    public $chunkSizeBytes;
449
450
    /**
451
     * READ-ONLY: The policy used for change-tracking on entities of this class.
452
     *
453
     * @var int
454
     */
455
    public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;
456
457
    /**
458
     * READ-ONLY: A flag for whether or not instances of this class are to be versioned
459
     * with optimistic locking.
460
     *
461
     * @var bool $isVersioned
462
     */
463
    public $isVersioned = false;
464
465
    /**
466
     * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
467
     *
468
     * @var string|null $versionField
469
     */
470
    public $versionField;
471
472
    /**
473
     * READ-ONLY: A flag for whether or not instances of this class are to allow pessimistic
474
     * locking.
475
     *
476
     * @var bool $isLockable
477
     */
478
    public $isLockable = false;
479
480
    /**
481
     * READ-ONLY: The name of the field which is used for locking a document.
482
     *
483
     * @var mixed $lockField
484
     */
485
    public $lockField;
486
487
    /**
488
     * The ReflectionClass instance of the mapped class.
489
     *
490
     * @var ReflectionClass
491
     */
492
    public $reflClass;
493
494
    /**
495
     * READ_ONLY: A flag for whether or not this document is read-only.
496
     *
497
     * @var bool
498
     */
499
    public $isReadOnly;
500
501
    /** @var InstantiatorInterface */
502
    private $instantiator;
503
504
    /**
505
     * Initializes a new ClassMetadata instance that will hold the object-document mapping
506
     * metadata of the class with the given name.
507
     */
508 1627
    public function __construct(string $documentName)
509
    {
510 1627
        $this->name             = $documentName;
511 1627
        $this->rootDocumentName = $documentName;
512 1627
        $this->reflClass        = new ReflectionClass($documentName);
513 1627
        $this->setCollection($this->reflClass->getShortName());
514 1627
        $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...\InstantiatorInterface> 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...
515 1627
    }
516
517
    /**
518
     * Helper method to get reference id of ref* type references
519
     *
520
     * @internal
521
     *
522
     * @param mixed $reference
523
     *
524
     * @return mixed
525
     */
526 118
    public static function getReferenceId($reference, string $storeAs)
527
    {
528 118
        return $storeAs === self::REFERENCE_STORE_AS_ID ? $reference : $reference[self::getReferencePrefix($storeAs) . 'id'];
529
    }
530
531
    /**
532
     * Returns the reference prefix used for a reference
533
     */
534 187
    private static function getReferencePrefix(string $storeAs) : string
535
    {
536 187
        if (! in_array($storeAs, [self::REFERENCE_STORE_AS_REF, self::REFERENCE_STORE_AS_DB_REF, self::REFERENCE_STORE_AS_DB_REF_WITH_DB])) {
537
            throw new LogicException('Can only get a reference prefix for DBRef and reference arrays');
538
        }
539
540 187
        return $storeAs === self::REFERENCE_STORE_AS_REF ? '' : '$';
541
    }
542
543
    /**
544
     * Returns a fully qualified field name for a given reference
545
     *
546
     * @internal
547
     *
548
     * @param string $pathPrefix The field path prefix
549
     */
550 142
    public static function getReferenceFieldName(string $storeAs, string $pathPrefix = '') : string
551
    {
552 142
        if ($storeAs === self::REFERENCE_STORE_AS_ID) {
553 101
            return $pathPrefix;
554
        }
555
556 126
        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...
557
    }
558
559
    /**
560
     * {@inheritDoc}
561
     */
562 1555
    public function getReflectionClass() : ReflectionClass
563
    {
564 1555
        return $this->reflClass;
565
    }
566
567
    /**
568
     * {@inheritDoc}
569
     */
570 348
    public function isIdentifier($fieldName) : bool
571
    {
572 348
        return $this->identifier === $fieldName;
573
    }
574
575
    /**
576
     * Sets the mapped identifier field of this class.
577
     *
578
     * @internal
579
     */
580 1012
    public function setIdentifier(?string $identifier) : void
581
    {
582 1012
        $this->identifier = $identifier;
583 1012
    }
584
585
    /**
586
     * {@inheritDoc}
587
     *
588
     * Since MongoDB only allows exactly one identifier field
589
     * this will always return an array with only one value
590
     */
591 11
    public function getIdentifier() : array
592
    {
593 11
        return [$this->identifier];
594
    }
595
596
    /**
597
     * Since MongoDB only allows exactly one identifier field
598
     * this will always return an array with only one value
599
     *
600
     * return (string|null)[]
601
     */
602 101
    public function getIdentifierFieldNames() : array
603
    {
604 101
        return [$this->identifier];
605
    }
606
607
    /**
608
     * {@inheritDoc}
609
     */
610 990
    public function hasField($fieldName) : bool
611
    {
612 990
        return isset($this->fieldMappings[$fieldName]);
613
    }
614
615
    /**
616
     * Sets the inheritance type used by the class and it's subclasses.
617
     */
618 1033
    public function setInheritanceType(int $type) : void
619
    {
620 1033
        $this->inheritanceType = $type;
621 1033
    }
622
623
    /**
624
     * Checks whether a mapped field is inherited from an entity superclass.
625
     */
626 1544
    public function isInheritedField(string $fieldName) : bool
627
    {
628 1544
        return isset($this->fieldMappings[$fieldName]['inherited']);
629
    }
630
631
    /**
632
     * Registers a custom repository class for the document class.
633
     */
634 956
    public function setCustomRepositoryClass(?string $repositoryClassName) : void
635
    {
636 956
        if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
637
            return;
638
        }
639
640 956
        $this->customRepositoryClassName = $repositoryClassName;
641 956
    }
642
643
    /**
644
     * Dispatches the lifecycle event of the given document by invoking all
645
     * registered callbacks.
646
     *
647
     * @throws InvalidArgumentException If document class is not this class or
648
     *                                   a Proxy of this class.
649
     */
650 658
    public function invokeLifecycleCallbacks(string $event, object $document, ?array $arguments = null) : void
651
    {
652 658
        if (! $document instanceof $this->name) {
653 1
            throw new InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"', $this->name, get_class($document)));
654
        }
655
656 657
        if (empty($this->lifecycleCallbacks[$event])) {
657 642
            return;
658
        }
659
660 191
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
661 191
            if ($arguments !== null) {
662 190
                $document->$callback(...$arguments);
663
            } else {
664 2
                $document->$callback();
665
            }
666
        }
667 191
    }
668
669
    /**
670
     * Checks whether the class has callbacks registered for a lifecycle event.
671
     */
672
    public function hasLifecycleCallbacks(string $event) : bool
673
    {
674
        return ! empty($this->lifecycleCallbacks[$event]);
675
    }
676
677
    /**
678
     * Gets the registered lifecycle callbacks for an event.
679
     */
680
    public function getLifecycleCallbacks(string $event) : array
681
    {
682
        return $this->lifecycleCallbacks[$event] ?? [];
683
    }
684
685
    /**
686
     * Adds a lifecycle callback for documents of this class.
687
     *
688
     * If the callback is already registered, this is a NOOP.
689
     */
690 924
    public function addLifecycleCallback(string $callback, string $event) : void
691
    {
692 924
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
693 1
            return;
694
        }
695
696 924
        $this->lifecycleCallbacks[$event][] = $callback;
697 924
    }
698
699
    /**
700
     * Sets the lifecycle callbacks for documents of this class.
701
     *
702
     * Any previously registered callbacks are overwritten.
703
     */
704 1011
    public function setLifecycleCallbacks(array $callbacks) : void
705
    {
706 1011
        $this->lifecycleCallbacks = $callbacks;
707 1011
    }
708
709
    /**
710
     * Registers a method for loading document data before field hydration.
711
     *
712
     * Note: A method may be registered multiple times for different fields.
713
     * it will be invoked only once for the first field found.
714
     *
715
     * @param array|string $fields Database field name(s)
716
     */
717 14
    public function registerAlsoLoadMethod(string $method, $fields) : void
718
    {
719 14
        $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : [$fields];
720 14
    }
721
722
    /**
723
     * Sets the AlsoLoad methods for documents of this class.
724
     *
725
     * Any previously registered methods are overwritten.
726
     */
727 1011
    public function setAlsoLoadMethods(array $methods) : void
728
    {
729 1011
        $this->alsoLoadMethods = $methods;
730 1011
    }
731
732
    /**
733
     * Sets the discriminator field.
734
     *
735
     * The field name is the the unmapped database field. Discriminator values
736
     * are only used to discern the hydration class and are not mapped to class
737
     * properties.
738
     *
739
     * @param array|string|null $discriminatorField
740
     *
741
     * @throws MappingException If the discriminator field conflicts with the
742
     *                          "name" attribute of a mapped field.
743
     */
744 1042
    public function setDiscriminatorField($discriminatorField) : void
745
    {
746 1042
        if ($this->isFile) {
747
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
748
        }
749
750 1042
        if ($discriminatorField === null) {
751 962
            $this->discriminatorField = null;
752
753 962
            return;
754
        }
755
756
        // @todo: deprecate, document and remove this:
757
        // Handle array argument with name/fieldName keys for BC
758 197
        if (is_array($discriminatorField)) {
759
            if (isset($discriminatorField['name'])) {
760
                $discriminatorField = $discriminatorField['name'];
761
            } elseif (isset($discriminatorField['fieldName'])) {
762
                $discriminatorField = $discriminatorField['fieldName'];
763
            }
764
        }
765
766 197
        foreach ($this->fieldMappings as $fieldMapping) {
767 4
            if ($discriminatorField === $fieldMapping['name']) {
768 1
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
769
            }
770
        }
771
772 196
        $this->discriminatorField = $discriminatorField;
773 196
    }
774
775
    /**
776
     * Sets the discriminator values used by this class.
777
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
778
     *
779
     * @throws MappingException
780
     */
781 1031
    public function setDiscriminatorMap(array $map) : void
782
    {
783 1031
        if ($this->isFile) {
784
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
785
        }
786
787 1031
        $this->subClasses         = [];
788 1031
        $this->discriminatorMap   = [];
789 1031
        $this->discriminatorValue = null;
790
791 1031
        foreach ($map as $value => $className) {
792 184
            $this->discriminatorMap[$value] = $className;
793 184
            if ($this->name === $className) {
794 175
                $this->discriminatorValue = $value;
795
            } else {
796 182
                if (! class_exists($className)) {
797
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
798
                }
799 182
                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 167
                    $this->subClasses[] = $className;
801
                }
802
            }
803
        }
804 1031
    }
805
806
    /**
807
     * Sets the default discriminator value to be used for this class
808
     * Used for SINGLE_TABLE inheritance mapping strategies if the document has no discriminator value
809
     *
810
     * @throws MappingException
811
     */
812 1014
    public function setDefaultDiscriminatorValue(?string $defaultDiscriminatorValue) : void
813
    {
814 1014
        if ($this->isFile) {
815
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
816
        }
817
818 1014
        if ($defaultDiscriminatorValue === null) {
819 1011
            $this->defaultDiscriminatorValue = null;
820
821 1011
            return;
822
        }
823
824 116
        if (! array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
825
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
826
        }
827
828 116
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
829 116
    }
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 4
    public function setDiscriminatorValue(string $value) : void
839
    {
840 4
        if ($this->isFile) {
841
            throw MappingException::discriminatorNotAllowedForGridFS($this->name);
842
        }
843
844 4
        $this->discriminatorMap[$value] = $this->name;
845 4
        $this->discriminatorValue       = $value;
846 4
    }
847
848
    /**
849
     * Add a index for this Document.
850
     */
851 267
    public function addIndex(array $keys, array $options = []) : void
852
    {
853 267
        $this->indexes[] = [
854
            'keys' => array_map(static function ($value) {
855 267
                if ($value === 1 || $value === -1) {
856 113
                    return $value;
857
                }
858 267
                if (is_string($value)) {
859 267
                    $lower = strtolower($value);
860 267
                    if ($lower === 'asc') {
861 260
                        return 1;
862
                    }
863
864 120
                    if ($lower === 'desc') {
865
                        return -1;
866
                    }
867
                }
868 120
                return $value;
869 267
            }, $keys),
870 267
            'options' => $options,
871
        ];
872 267
    }
873
874
    /**
875
     * Returns the array of indexes for this Document.
876
     */
877 50
    public function getIndexes() : array
878
    {
879 50
        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 149
    public function setShardKey(array $keys, array $options = []) : void
896
    {
897 149
        if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && $this->shardKey !== []) {
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 149
        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 147
        foreach (array_keys($keys) as $field) {
906 147
            if (! isset($this->fieldMappings[$field])) {
907 140
                continue;
908
            }
909
910 119
            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 116
            if ($this->fieldMappings[$field]['strategy'] !== static::STORAGE_STRATEGY_SET) {
915 1
                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 143
        $this->shardKey = [
920
            'keys' => array_map(static function ($value) {
921 143
                if ($value === 1 || $value === -1) {
922 5
                    return $value;
923
                }
924 143
                if (is_string($value)) {
925 143
                    $lower = strtolower($value);
926 143
                    if ($lower === 'asc') {
927 141
                        return 1;
928
                    }
929
930 115
                    if ($lower === 'desc') {
931
                        return -1;
932
                    }
933
                }
934 115
                return $value;
935 143
            }, $keys),
936 143
            'options' => $options,
937
        ];
938 143
    }
939
940 27
    public function getShardKey() : array
941
    {
942 27
        return $this->shardKey;
943
    }
944
945
    /**
946
     * Checks whether this document has shard key or not.
947
     */
948 1265
    public function isSharded() : bool
949
    {
950 1265
        return $this->shardKey ? true : false;
951
    }
952
953
    /**
954
     * Sets the read preference used by this class.
955
     */
956 1011
    public function setReadPreference(?string $readPreference, array $tags) : void
957
    {
958 1011
        $this->readPreference     = $readPreference;
959 1011
        $this->readPreferenceTags = $tags;
960 1011
    }
961
962
    /**
963
     * Sets the write concern used by this class.
964
     *
965
     * @param string|int|null $writeConcern
966
     */
967 1021
    public function setWriteConcern($writeConcern) : void
968
    {
969 1021
        $this->writeConcern = $writeConcern;
970 1021
    }
971
972
    /**
973
     * @return int|string|null
974
     */
975 11
    public function getWriteConcern()
976
    {
977 11
        return $this->writeConcern;
978
    }
979
980
    /**
981
     * Whether there is a write concern configured for this class.
982
     */
983 606
    public function hasWriteConcern() : bool
984
    {
985 606
        return $this->writeConcern !== null;
986
    }
987
988
    /**
989
     * Sets the change tracking policy used by this class.
990
     */
991 1012
    public function setChangeTrackingPolicy(int $policy) : void
992
    {
993 1012
        $this->changeTrackingPolicy = $policy;
994 1012
    }
995
996
    /**
997
     * Whether the change tracking policy of this class is "deferred explicit".
998
     */
999 68
    public function isChangeTrackingDeferredExplicit() : bool
1000
    {
1001 68
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT;
1002
    }
1003
1004
    /**
1005
     * Whether the change tracking policy of this class is "deferred implicit".
1006
     */
1007 617
    public function isChangeTrackingDeferredImplicit() : bool
1008
    {
1009 617
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT;
1010
    }
1011
1012
    /**
1013
     * Whether the change tracking policy of this class is "notify".
1014
     */
1015 341
    public function isChangeTrackingNotify() : bool
1016
    {
1017 341
        return $this->changeTrackingPolicy === self::CHANGETRACKING_NOTIFY;
1018
    }
1019
1020
    /**
1021
     * Gets the ReflectionProperties of the mapped class.
1022
     */
1023 1
    public function getReflectionProperties() : array
1024
    {
1025 1
        return $this->reflFields;
1026
    }
1027
1028
    /**
1029
     * Gets a ReflectionProperty for a specific field of the mapped class.
1030
     */
1031 100
    public function getReflectionProperty(string $name) : ReflectionProperty
1032
    {
1033 100
        return $this->reflFields[$name];
1034
    }
1035
1036
    /**
1037
     * {@inheritDoc}
1038
     */
1039 1554
    public function getName() : string
1040
    {
1041 1554
        return $this->name;
1042
    }
1043
1044
    /**
1045
     * Returns the database this Document is mapped to.
1046
     */
1047 1460
    public function getDatabase() : ?string
1048
    {
1049 1460
        return $this->db;
1050
    }
1051
1052
    /**
1053
     * Set the database this Document is mapped to.
1054
     */
1055 165
    public function setDatabase(?string $db) : void
1056
    {
1057 165
        $this->db = $db;
1058 165
    }
1059
1060
    /**
1061
     * Get the collection this Document is mapped to.
1062
     */
1063 1458
    public function getCollection() : string
1064
    {
1065 1458
        return $this->collection;
1066
    }
1067
1068
    /**
1069
     * Sets the collection this Document is mapped to.
1070
     *
1071
     * @param array|string $name
1072
     *
1073
     * @throws InvalidArgumentException
1074
     */
1075 1627
    public function setCollection($name) : void
1076
    {
1077 1627
        if (is_array($name)) {
1078 1
            if (! isset($name['name'])) {
1079
                throw new InvalidArgumentException('A name key is required when passing an array to setCollection()');
1080
            }
1081 1
            $this->collectionCapped = $name['capped'] ?? false;
1082 1
            $this->collectionSize   = $name['size'] ?? 0;
1083 1
            $this->collectionMax    = $name['max'] ?? 0;
1084 1
            $this->collection       = $name['name'];
1085
        } else {
1086 1627
            $this->collection = $name;
1087
        }
1088 1627
    }
1089
1090 123
    public function getBucketName() : ?string
1091
    {
1092 123
        return $this->bucketName;
1093
    }
1094
1095 1
    public function setBucketName(string $bucketName) : void
1096
    {
1097 1
        $this->bucketName = $bucketName;
1098 1
        $this->setCollection($bucketName . '.files');
1099 1
    }
1100
1101 11
    public function getChunkSizeBytes() : ?int
1102
    {
1103 11
        return $this->chunkSizeBytes;
1104
    }
1105
1106 127
    public function setChunkSizeBytes(int $chunkSizeBytes) : void
1107
    {
1108 127
        $this->chunkSizeBytes = $chunkSizeBytes;
1109 127
    }
1110
1111
    /**
1112
     * Get whether or not the documents collection is capped.
1113
     */
1114 11
    public function getCollectionCapped() : bool
1115
    {
1116 11
        return $this->collectionCapped;
1117
    }
1118
1119
    /**
1120
     * Set whether or not the documents collection is capped.
1121
     */
1122 1
    public function setCollectionCapped(bool $bool) : void
1123
    {
1124 1
        $this->collectionCapped = $bool;
1125 1
    }
1126
1127
    /**
1128
     * Get the collection size
1129
     */
1130 11
    public function getCollectionSize() : ?int
1131
    {
1132 11
        return $this->collectionSize;
1133
    }
1134
1135
    /**
1136
     * Set the collection size.
1137
     */
1138 1
    public function setCollectionSize(int $size) : void
1139
    {
1140 1
        $this->collectionSize = $size;
1141 1
    }
1142
1143
    /**
1144
     * Get the collection max.
1145
     */
1146 11
    public function getCollectionMax() : ?int
1147
    {
1148 11
        return $this->collectionMax;
1149
    }
1150
1151
    /**
1152
     * Set the collection max.
1153
     */
1154 1
    public function setCollectionMax(int $max) : void
1155
    {
1156 1
        $this->collectionMax = $max;
1157 1
    }
1158
1159
    /**
1160
     * Returns TRUE if this Document is mapped to a collection FALSE otherwise.
1161
     */
1162
    public function isMappedToCollection() : bool
1163
    {
1164
        return $this->collection ? true : false;
1165
    }
1166
1167
    /**
1168
     * Validates the storage strategy of a mapping for consistency
1169
     *
1170
     * @throws MappingException
1171
     */
1172 1571
    private function applyStorageStrategy(array &$mapping) : void
1173
    {
1174 1571
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1175 1550
            return;
1176
        }
1177
1178
        switch (true) {
1179 1532
            case $mapping['type'] === 'int':
1180 1531
            case $mapping['type'] === 'float':
1181 970
                $defaultStrategy   = self::STORAGE_STRATEGY_SET;
1182 970
                $allowedStrategies = [self::STORAGE_STRATEGY_SET, self::STORAGE_STRATEGY_INCREMENT];
1183 970
                break;
1184
1185 1531
            case $mapping['type'] === 'many':
1186 1220
                $defaultStrategy   = CollectionHelper::DEFAULT_STRATEGY;
1187
                $allowedStrategies = [
1188 1220
                    self::STORAGE_STRATEGY_PUSH_ALL,
1189 1220
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1190 1220
                    self::STORAGE_STRATEGY_SET,
1191 1220
                    self::STORAGE_STRATEGY_SET_ARRAY,
1192 1220
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1193 1220
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1194
                ];
1195 1220
                break;
1196
1197
            default:
1198 1518
                $defaultStrategy   = self::STORAGE_STRATEGY_SET;
1199 1518
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1200
        }
1201
1202 1532
        if (! isset($mapping['strategy'])) {
1203 1524
            $mapping['strategy'] = $defaultStrategy;
1204
        }
1205
1206 1532
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1207
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1208
        }
1209
1210 1532
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1211 1532
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1212 1
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1213
        }
1214 1531
    }
1215
1216
    /**
1217
     * Map a single embedded document.
1218
     */
1219 6
    public function mapOneEmbedded(array $mapping) : void
1220
    {
1221 6
        $mapping['embedded'] = true;
1222 6
        $mapping['type']     = 'one';
1223 6
        $this->mapField($mapping);
1224 5
    }
1225
1226
    /**
1227
     * Map a collection of embedded documents.
1228
     */
1229 6
    public function mapManyEmbedded(array $mapping) : void
1230
    {
1231 6
        $mapping['embedded'] = true;
1232 6
        $mapping['type']     = 'many';
1233 6
        $this->mapField($mapping);
1234 6
    }
1235
1236
    /**
1237
     * Map a single document reference.
1238
     */
1239 3
    public function mapOneReference(array $mapping) : void
1240
    {
1241 3
        $mapping['reference'] = true;
1242 3
        $mapping['type']      = 'one';
1243 3
        $this->mapField($mapping);
1244 3
    }
1245
1246
    /**
1247
     * Map a collection of document references.
1248
     */
1249 1
    public function mapManyReference(array $mapping) : void
1250
    {
1251 1
        $mapping['reference'] = true;
1252 1
        $mapping['type']      = 'many';
1253 1
        $this->mapField($mapping);
1254 1
    }
1255
1256
    /**
1257
     * Adds a field mapping without completing/validating it.
1258
     * This is mainly used to add inherited field mappings to derived classes.
1259
     *
1260
     * @internal
1261
     */
1262 190
    public function addInheritedFieldMapping(array $fieldMapping) : void
1263
    {
1264 190
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1265
1266 190
        if (! isset($fieldMapping['association'])) {
1267 190
            return;
1268
        }
1269
1270 140
        $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1271 140
    }
1272
1273
    /**
1274
     * Adds an association mapping without completing/validating it.
1275
     * This is mainly used to add inherited association mappings to derived classes.
1276
     *
1277
     * @internal
1278
     *
1279
     * @throws MappingException
1280
     */
1281 141
    public function addInheritedAssociationMapping(array $mapping) : void
1282
    {
1283 141
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1284 141
    }
1285
1286
    /**
1287
     * Checks whether the class has a mapped association with the given field name.
1288
     */
1289 31
    public function hasReference(string $fieldName) : bool
1290
    {
1291 31
        return isset($this->fieldMappings[$fieldName]['reference']);
1292
    }
1293
1294
    /**
1295
     * Checks whether the class has a mapped embed with the given field name.
1296
     */
1297 4
    public function hasEmbed(string $fieldName) : bool
1298
    {
1299 4
        return isset($this->fieldMappings[$fieldName]['embedded']);
1300
    }
1301
1302
    /**
1303
     * {@inheritDoc}
1304
     *
1305
     * Checks whether the class has a mapped association (embed or reference) with the given field name.
1306
     */
1307 6
    public function hasAssociation($fieldName) : bool
1308
    {
1309 6
        return $this->hasReference($fieldName) || $this->hasEmbed($fieldName);
1310
    }
1311
1312
    /**
1313
     * {@inheritDoc}
1314
     *
1315
     * Checks whether the class has a mapped reference or embed for the specified field and
1316
     * is a single valued association.
1317
     */
1318
    public function isSingleValuedAssociation($fieldName) : bool
1319
    {
1320
        return $this->isSingleValuedReference($fieldName) || $this->isSingleValuedEmbed($fieldName);
1321
    }
1322
1323
    /**
1324
     * {@inheritDoc}
1325
     *
1326
     * Checks whether the class has a mapped reference or embed for the specified field and
1327
     * is a collection valued association.
1328
     */
1329
    public function isCollectionValuedAssociation($fieldName) : bool
1330
    {
1331
        return $this->isCollectionValuedReference($fieldName) || $this->isCollectionValuedEmbed($fieldName);
1332
    }
1333
1334
    /**
1335
     * Checks whether the class has a mapped association for the specified field
1336
     * and if yes, checks whether it is a single-valued association (to-one).
1337
     */
1338 1
    public function isSingleValuedReference(string $fieldName) : bool
1339
    {
1340 1
        return isset($this->fieldMappings[$fieldName]['association']) &&
1341 1
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_ONE;
1342
    }
1343
1344
    /**
1345
     * Checks whether the class has a mapped association for the specified field
1346
     * and if yes, checks whether it is a collection-valued association (to-many).
1347
     */
1348
    public function isCollectionValuedReference(string $fieldName) : bool
1349
    {
1350
        return isset($this->fieldMappings[$fieldName]['association']) &&
1351
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_MANY;
1352
    }
1353
1354
    /**
1355
     * Checks whether the class has a mapped embedded document for the specified field
1356
     * and if yes, checks whether it is a single-valued association (to-one).
1357
     */
1358
    public function isSingleValuedEmbed(string $fieldName) : bool
1359
    {
1360
        return isset($this->fieldMappings[$fieldName]['association']) &&
1361
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_ONE;
1362
    }
1363
1364
    /**
1365
     * Checks whether the class has a mapped embedded document for the specified field
1366
     * and if yes, checks whether it is a collection-valued association (to-many).
1367
     */
1368
    public function isCollectionValuedEmbed(string $fieldName) : bool
1369
    {
1370
        return isset($this->fieldMappings[$fieldName]['association']) &&
1371
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_MANY;
1372
    }
1373
1374
    /**
1375
     * Sets the ID generator used to generate IDs for instances of this class.
1376
     */
1377 1490
    public function setIdGenerator(AbstractIdGenerator $generator) : void
1378
    {
1379 1490
        $this->idGenerator = $generator;
1380 1490
    }
1381
1382
    /**
1383
     * Casts the identifier to its portable PHP type.
1384
     *
1385
     * @param mixed $id
1386
     *
1387
     * @return mixed $id
1388
     */
1389 654
    public function getPHPIdentifierValue($id)
1390
    {
1391 654
        $idType = $this->fieldMappings[$this->identifier]['type'];
1392 654
        return Type::getType($idType)->convertToPHPValue($id);
1393
    }
1394
1395
    /**
1396
     * Casts the identifier to its database type.
1397
     *
1398
     * @param mixed $id
1399
     *
1400
     * @return mixed $id
1401
     */
1402 720
    public function getDatabaseIdentifierValue($id)
1403
    {
1404 720
        $idType = $this->fieldMappings[$this->identifier]['type'];
1405 720
        return Type::getType($idType)->convertToDatabaseValue($id);
1406
    }
1407
1408
    /**
1409
     * Sets the document identifier of a document.
1410
     *
1411
     * The value will be converted to a PHP type before being set.
1412
     *
1413
     * @param mixed $id
1414
     */
1415 584
    public function setIdentifierValue(object $document, $id) : void
1416
    {
1417 584
        $id = $this->getPHPIdentifierValue($id);
1418 584
        $this->reflFields[$this->identifier]->setValue($document, $id);
1419 584
    }
1420
1421
    /**
1422
     * Gets the document identifier as a PHP type.
1423
     *
1424
     * @return mixed $id
1425
     */
1426 663
    public function getIdentifierValue(object $document)
1427
    {
1428 663
        return $this->reflFields[$this->identifier]->getValue($document);
1429
    }
1430
1431
    /**
1432
     * {@inheritDoc}
1433
     *
1434
     * Since MongoDB only allows exactly one identifier field this is a proxy
1435
     * to {@see getIdentifierValue()} and returns an array with the identifier
1436
     * field as a key.
1437
     */
1438
    public function getIdentifierValues($object) : array
1439
    {
1440
        return [$this->identifier => $this->getIdentifierValue($object)];
1441
    }
1442
1443
    /**
1444
     * Get the document identifier object as a database type.
1445
     *
1446
     * @return mixed $id
1447
     */
1448 29
    public function getIdentifierObject(object $document)
1449
    {
1450 29
        return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));
1451
    }
1452
1453
    /**
1454
     * Sets the specified field to the specified value on the given document.
1455
     *
1456
     * @param mixed $value
1457
     */
1458 8
    public function setFieldValue(object $document, string $field, $value) : void
1459
    {
1460 8
        if ($document instanceof GhostObjectInterface && ! $document->isProxyInitialized()) {
0 ignored issues
show
Bug introduced by
The class ProxyManager\Proxy\GhostObjectInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
1461
            //property changes to an uninitialized proxy will not be tracked or persisted,
1462
            //so the proxy needs to be loaded first.
1463 1
            $document->initializeProxy();
1464
        }
1465
1466 8
        $this->reflFields[$field]->setValue($document, $value);
1467 8
    }
1468
1469
    /**
1470
     * Gets the specified field's value off the given document.
1471
     *
1472
     * @return mixed
1473
     */
1474 33
    public function getFieldValue(object $document, string $field)
1475
    {
1476 33
        if ($document instanceof GhostObjectInterface && $field !== $this->identifier && ! $document->isProxyInitialized()) {
0 ignored issues
show
Bug introduced by
The class ProxyManager\Proxy\GhostObjectInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
1477 1
            $document->initializeProxy();
1478
        }
1479
1480 33
        return $this->reflFields[$field]->getValue($document);
1481
    }
1482
1483
    /**
1484
     * Gets the mapping of a field.
1485
     *
1486
     * @throws MappingException If the $fieldName is not found in the fieldMappings array.
1487
     */
1488 199
    public function getFieldMapping(string $fieldName) : array
1489
    {
1490 199
        if (! isset($this->fieldMappings[$fieldName])) {
1491 6
            throw MappingException::mappingNotFound($this->name, $fieldName);
1492
        }
1493 197
        return $this->fieldMappings[$fieldName];
1494
    }
1495
1496
    /**
1497
     * Gets mappings of fields holding embedded document(s).
1498
     */
1499 595
    public function getEmbeddedFieldsMappings() : array
1500
    {
1501 595
        return array_filter(
1502 595
            $this->associationMappings,
1503
            static function ($assoc) {
1504 455
                return ! empty($assoc['embedded']);
1505 595
            }
1506
        );
1507
    }
1508
1509
    /**
1510
     * Gets the field mapping by its DB name.
1511
     * E.g. it returns identifier's mapping when called with _id.
1512
     *
1513
     * @throws MappingException
1514
     */
1515 17
    public function getFieldMappingByDbFieldName(string $dbFieldName) : array
1516
    {
1517 17
        foreach ($this->fieldMappings as $mapping) {
1518 17
            if ($mapping['name'] === $dbFieldName) {
1519 15
                return $mapping;
1520
            }
1521
        }
1522
1523 2
        throw MappingException::mappingNotFoundByDbName($this->name, $dbFieldName);
1524
    }
1525
1526
    /**
1527
     * Check if the field is not null.
1528
     */
1529 1
    public function isNullable(string $fieldName) : bool
1530
    {
1531 1
        $mapping = $this->getFieldMapping($fieldName);
1532 1
        return isset($mapping['nullable']) && $mapping['nullable'] === true;
1533
    }
1534
1535
    /**
1536
     * Checks whether the document has a discriminator field and value configured.
1537
     */
1538
    public function hasDiscriminator() : bool
1539
    {
1540
        return isset($this->discriminatorField, $this->discriminatorValue);
1541
    }
1542
1543
    /**
1544
     * Sets the type of Id generator to use for the mapped class.
1545
     */
1546 1011
    public function setIdGeneratorType(int $generatorType) : void
1547
    {
1548 1011
        $this->generatorType = $generatorType;
1549 1011
    }
1550
1551
    /**
1552
     * Sets the Id generator options.
1553
     */
1554
    public function setIdGeneratorOptions(array $generatorOptions) : void
1555
    {
1556
        $this->generatorOptions = $generatorOptions;
1557
    }
1558
1559 622
    public function isInheritanceTypeNone() : bool
1560
    {
1561 622
        return $this->inheritanceType === self::INHERITANCE_TYPE_NONE;
1562
    }
1563
1564
    /**
1565
     * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.
1566
     */
1567 1010
    public function isInheritanceTypeSingleCollection() : bool
1568
    {
1569 1010
        return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION;
1570
    }
1571
1572
    /**
1573
     * Checks whether the mapped class uses the COLLECTION_PER_CLASS inheritance mapping strategy.
1574
     */
1575
    public function isInheritanceTypeCollectionPerClass() : bool
1576
    {
1577
        return $this->inheritanceType === self::INHERITANCE_TYPE_COLLECTION_PER_CLASS;
1578
    }
1579
1580
    /**
1581
     * Sets the mapped subclasses of this class.
1582
     *
1583
     * @param string[] $subclasses The names of all mapped subclasses.
1584
     */
1585 2
    public function setSubclasses(array $subclasses) : void
1586
    {
1587 2
        foreach ($subclasses as $subclass) {
1588 2
            $this->subClasses[] = $subclass;
1589
        }
1590 2
    }
1591
1592
    /**
1593
     * Sets the parent class names.
1594
     * Assumes that the class names in the passed array are in the order:
1595
     * directParent -> directParentParent -> directParentParentParent ... -> root.
1596
     *
1597
     * @param string[] $classNames
1598
     */
1599 1543
    public function setParentClasses(array $classNames) : void
1600
    {
1601 1543
        $this->parentClasses = $classNames;
1602
1603 1543
        if (count($classNames) <= 0) {
1604 1542
            return;
1605
        }
1606
1607 174
        $this->rootDocumentName = (string) array_pop($classNames);
1608 174
    }
1609
1610
    /**
1611
     * Checks whether the class will generate a new \MongoDB\BSON\ObjectId instance for us.
1612
     */
1613
    public function isIdGeneratorAuto() : bool
1614
    {
1615
        return $this->generatorType === self::GENERATOR_TYPE_AUTO;
1616
    }
1617
1618
    /**
1619
     * Checks whether the class will use a collection to generate incremented identifiers.
1620
     */
1621
    public function isIdGeneratorIncrement() : bool
1622
    {
1623
        return $this->generatorType === self::GENERATOR_TYPE_INCREMENT;
1624
    }
1625
1626
    /**
1627
     * Checks whether the class will generate a uuid id.
1628
     */
1629
    public function isIdGeneratorUuid() : bool
1630
    {
1631
        return $this->generatorType === self::GENERATOR_TYPE_UUID;
1632
    }
1633
1634
    /**
1635
     * Checks whether the class uses no id generator.
1636
     */
1637
    public function isIdGeneratorNone() : bool
1638
    {
1639
        return $this->generatorType === self::GENERATOR_TYPE_NONE;
1640
    }
1641
1642
    /**
1643
     * Sets the version field mapping used for versioning. Sets the default
1644
     * value to use depending on the column type.
1645
     *
1646
     * @throws LockException
1647
     */
1648 156
    public function setVersionMapping(array &$mapping) : void
1649
    {
1650 156
        if ($mapping['type'] !== 'int' && $mapping['type'] !== 'date') {
1651 1
            throw LockException::invalidVersionFieldType($mapping['type']);
1652
        }
1653
1654 155
        $this->isVersioned  = true;
1655 155
        $this->versionField = $mapping['fieldName'];
1656 155
    }
1657
1658
    /**
1659
     * Sets whether this class is to be versioned for optimistic locking.
1660
     */
1661 1011
    public function setVersioned(bool $bool) : void
1662
    {
1663 1011
        $this->isVersioned = $bool;
1664 1011
    }
1665
1666
    /**
1667
     * Sets the name of the field that is to be used for versioning if this class is
1668
     * versioned for optimistic locking.
1669
     */
1670 1011
    public function setVersionField(?string $versionField) : void
1671
    {
1672 1011
        $this->versionField = $versionField;
1673 1011
    }
1674
1675
    /**
1676
     * Sets the version field mapping used for versioning. Sets the default
1677
     * value to use depending on the column type.
1678
     *
1679
     * @throws LockException
1680
     */
1681 22
    public function setLockMapping(array &$mapping) : void
1682
    {
1683 22
        if ($mapping['type'] !== 'int') {
1684 1
            throw LockException::invalidLockFieldType($mapping['type']);
1685
        }
1686
1687 21
        $this->isLockable = true;
1688 21
        $this->lockField  = $mapping['fieldName'];
1689 21
    }
1690
1691
    /**
1692
     * Sets whether this class is to allow pessimistic locking.
1693
     */
1694
    public function setLockable(bool $bool) : void
1695
    {
1696
        $this->isLockable = $bool;
1697
    }
1698
1699
    /**
1700
     * Sets the name of the field that is to be used for storing whether a document
1701
     * is currently locked or not.
1702
     */
1703
    public function setLockField(string $lockField) : void
1704
    {
1705
        $this->lockField = $lockField;
1706
    }
1707
1708
    /**
1709
     * Marks this class as read only, no change tracking is applied to it.
1710
     */
1711 5
    public function markReadOnly() : void
1712
    {
1713 5
        $this->isReadOnly = true;
1714 5
    }
1715
1716
    /**
1717
     * {@inheritDoc}
1718
     */
1719
    public function getFieldNames() : array
1720
    {
1721
        return array_keys($this->fieldMappings);
1722
    }
1723
1724
    /**
1725
     * {@inheritDoc}
1726
     */
1727
    public function getAssociationNames() : array
1728
    {
1729
        return array_keys($this->associationMappings);
1730
    }
1731
1732
    /**
1733
     * {@inheritDoc}
1734
     */
1735
    public function getTypeOfField($fieldName) : ?string
1736
    {
1737
        return isset($this->fieldMappings[$fieldName]) ?
1738
            $this->fieldMappings[$fieldName]['type'] : null;
1739
    }
1740
1741
    /**
1742
     * {@inheritDoc}
1743
     */
1744 5
    public function getAssociationTargetClass($assocName) : ?string
1745
    {
1746 5
        if (! isset($this->associationMappings[$assocName])) {
1747 2
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1748
        }
1749
1750 3
        return $this->associationMappings[$assocName]['targetDocument'];
1751
    }
1752
1753
    /**
1754
     * Retrieve the collectionClass associated with an association
1755
     */
1756
    public function getAssociationCollectionClass(string $assocName) : string
1757
    {
1758
        if (! isset($this->associationMappings[$assocName])) {
1759
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1760
        }
1761
1762
        if (! array_key_exists('collectionClass', $this->associationMappings[$assocName])) {
1763
            throw new InvalidArgumentException("collectionClass can only be applied to 'embedMany' and 'referenceMany' associations.");
1764
        }
1765
1766
        return $this->associationMappings[$assocName]['collectionClass'];
1767
    }
1768
1769
    /**
1770
     * {@inheritDoc}
1771
     */
1772
    public function isAssociationInverseSide($fieldName) : bool
1773
    {
1774
        throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1775
    }
1776
1777
    /**
1778
     * {@inheritDoc}
1779
     */
1780
    public function getAssociationMappedByTargetField($fieldName)
1781
    {
1782
        throw new BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1783
    }
1784
1785
    /**
1786
     * Map a field.
1787
     *
1788
     * @throws MappingException
1789
     */
1790 1587
    public function mapField(array $mapping) : array
1791
    {
1792 1587
        if (! isset($mapping['fieldName']) && isset($mapping['name'])) {
1793 5
            $mapping['fieldName'] = $mapping['name'];
1794
        }
1795 1587
        if (! isset($mapping['fieldName']) || ! is_string($mapping['fieldName'])) {
1796
            throw MappingException::missingFieldName($this->name);
1797
        }
1798 1587
        if (! isset($mapping['name'])) {
1799 1586
            $mapping['name'] = $mapping['fieldName'];
1800
        }
1801
1802 1587
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1803 1
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, (string) $mapping['name']);
1804
        }
1805 1586
        if ($this->discriminatorField !== null && $this->discriminatorField === $mapping['name']) {
1806 1
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1807
        }
1808 1585
        if (isset($mapping['collectionClass'])) {
1809 121
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1810
        }
1811 1585
        if (! empty($mapping['collectionClass'])) {
1812 121
            $rColl = new ReflectionClass($mapping['collectionClass']);
1813 121
            if (! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1814 1
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1815
            }
1816
        }
1817
1818 1584
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1819 1
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1820
        }
1821
1822 1583
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : [];
1823
1824 1583
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
1825 1273
            $cascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1826
        }
1827
1828 1583
        if (isset($mapping['embedded'])) {
1829 1226
            unset($mapping['cascade']);
1830 1577
        } elseif (isset($mapping['cascade'])) {
1831 1034
            $mapping['cascade'] = $cascades;
1832
        }
1833
1834 1583
        $mapping['isCascadeRemove']  = in_array('remove', $cascades);
1835 1583
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1836 1583
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1837 1583
        $mapping['isCascadeMerge']   = in_array('merge', $cascades);
1838 1583
        $mapping['isCascadeDetach']  = in_array('detach', $cascades);
1839
1840 1583
        if (isset($mapping['id']) && $mapping['id'] === true) {
1841 1548
            $mapping['name']  = '_id';
1842 1548
            $this->identifier = $mapping['fieldName'];
1843 1548
            if (isset($mapping['strategy'])) {
1844 1543
                $this->generatorType = constant(self::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
1845
            }
1846 1548
            $this->generatorOptions = $mapping['options'] ?? [];
1847 1548
            switch ($this->generatorType) {
1848 1548
                case self::GENERATOR_TYPE_AUTO:
1849 1478
                    $mapping['type'] = 'id';
1850 1478
                    break;
1851
                default:
1852 211
                    if (! empty($this->generatorOptions['type'])) {
1853 56
                        $mapping['type'] = $this->generatorOptions['type'];
1854 155
                    } elseif (empty($mapping['type'])) {
1855 145
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
1856
                    }
1857
            }
1858 1548
            unset($this->generatorOptions['type']);
1859
        }
1860
1861 1583
        if (! isset($mapping['nullable'])) {
1862 40
            $mapping['nullable'] = false;
1863
        }
1864
1865 1583
        if (isset($mapping['reference'])
1866 1583
            && isset($mapping['storeAs'])
1867 1583
            && $mapping['storeAs'] === self::REFERENCE_STORE_AS_ID
1868 1583
            && ! isset($mapping['targetDocument'])
1869
        ) {
1870 3
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
1871
        }
1872
1873 1580
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
1874 1580
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
1875 4
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
1876
        }
1877
1878 1576
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
1879 1
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
1880
        }
1881
1882 1575
        if (isset($mapping['repositoryMethod']) && ! (empty($mapping['skip']) && empty($mapping['limit']) && empty($mapping['sort']))) {
1883 3
            throw MappingException::repositoryMethodCanNotBeCombinedWithSkipLimitAndSort($this->name, $mapping['fieldName']);
1884
        }
1885
1886 1572
        if (isset($mapping['reference']) && $mapping['type'] === 'one') {
1887 1144
            $mapping['association'] = self::REFERENCE_ONE;
1888
        }
1889 1572
        if (isset($mapping['reference']) && $mapping['type'] === 'many') {
1890 1083
            $mapping['association'] = self::REFERENCE_MANY;
1891
        }
1892 1572
        if (isset($mapping['embedded']) && $mapping['type'] === 'one') {
1893 1080
            $mapping['association'] = self::EMBED_ONE;
1894
        }
1895 1572
        if (isset($mapping['embedded']) && $mapping['type'] === 'many') {
1896 1122
            $mapping['association'] = self::EMBED_MANY;
1897
        }
1898
1899 1572
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
1900 954
            $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
1901
        }
1902
1903 1572
        if (isset($mapping['version'])) {
1904 156
            $mapping['notSaved'] = true;
1905 156
            $this->setVersionMapping($mapping);
1906
        }
1907 1572
        if (isset($mapping['lock'])) {
1908 22
            $mapping['notSaved'] = true;
1909 22
            $this->setLockMapping($mapping);
1910
        }
1911 1572
        $mapping['isOwningSide']  = true;
1912 1572
        $mapping['isInverseSide'] = false;
1913 1572
        if (isset($mapping['reference'])) {
1914 1211
            if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
1915 152
                $mapping['isOwningSide']  = true;
1916 152
                $mapping['isInverseSide'] = false;
1917
            }
1918 1211
            if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
1919 930
                $mapping['isInverseSide'] = true;
1920 930
                $mapping['isOwningSide']  = false;
1921
            }
1922 1211
            if (isset($mapping['repositoryMethod'])) {
1923 126
                $mapping['isInverseSide'] = true;
1924 126
                $mapping['isOwningSide']  = false;
1925
            }
1926 1211
            if (! isset($mapping['orphanRemoval'])) {
1927 1192
                $mapping['orphanRemoval'] = false;
1928
            }
1929
        }
1930
1931 1572
        if (! empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || ! $mapping['isInverseSide'])) {
1932
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
1933
        }
1934
1935 1572
        if ($this->isFile && ! $this->isAllowedGridFSField($mapping['name'])) {
1936 1
            throw MappingException::fieldNotAllowedForGridFS($this->name, $mapping['fieldName']);
1937
        }
1938
1939 1571
        $this->applyStorageStrategy($mapping);
1940 1570
        $this->checkDuplicateMapping($mapping);
1941
1942 1570
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
1943 1570
        if (isset($mapping['association'])) {
1944 1368
            $this->associationMappings[$mapping['fieldName']] = $mapping;
1945
        }
1946
1947 1570
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
1948 1569
        $reflProp->setAccessible(true);
1949 1569
        $this->reflFields[$mapping['fieldName']] = $reflProp;
1950
1951 1569
        return $mapping;
1952
    }
1953
1954
    /**
1955
     * Determines which fields get serialized.
1956
     *
1957
     * It is only serialized what is necessary for best unserialization performance.
1958
     * That means any metadata properties that are not set or empty or simply have
1959
     * their default value are NOT serialized.
1960
     *
1961
     * Parts that are also NOT serialized because they can not be properly unserialized:
1962
     *      - reflClass (ReflectionClass)
1963
     *      - reflFields (ReflectionProperty array)
1964
     *
1965
     * @return array The names of all the fields that should be serialized.
1966
     */
1967 6
    public function __sleep()
1968
    {
1969
        // This metadata is always serialized/cached.
1970
        $serialized = [
1971 6
            'fieldMappings',
1972
            'associationMappings',
1973
            'identifier',
1974
            'name',
1975
            'db',
1976
            'collection',
1977
            'readPreference',
1978
            'readPreferenceTags',
1979
            'writeConcern',
1980
            'rootDocumentName',
1981
            'generatorType',
1982
            'generatorOptions',
1983
            'idGenerator',
1984
            'indexes',
1985
            'shardKey',
1986
        ];
1987
1988
        // The rest of the metadata is only serialized if necessary.
1989 6
        if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {
1990
            $serialized[] = 'changeTrackingPolicy';
1991
        }
1992
1993 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...
1994 1
            $serialized[] = 'customRepositoryClassName';
1995
        }
1996
1997 6
        if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE || $this->discriminatorField !== null) {
1998 4
            $serialized[] = 'inheritanceType';
1999 4
            $serialized[] = 'discriminatorField';
2000 4
            $serialized[] = 'discriminatorValue';
2001 4
            $serialized[] = 'discriminatorMap';
2002 4
            $serialized[] = 'defaultDiscriminatorValue';
2003 4
            $serialized[] = 'parentClasses';
2004 4
            $serialized[] = 'subClasses';
2005
        }
2006
2007 6
        if ($this->isMappedSuperclass) {
2008 1
            $serialized[] = 'isMappedSuperclass';
2009
        }
2010
2011 6
        if ($this->isEmbeddedDocument) {
2012 1
            $serialized[] = 'isEmbeddedDocument';
2013
        }
2014
2015 6
        if ($this->isQueryResultDocument) {
2016
            $serialized[] = 'isQueryResultDocument';
2017
        }
2018
2019 6
        if ($this->isFile) {
2020
            $serialized[] = 'isFile';
2021
            $serialized[] = 'bucketName';
2022
            $serialized[] = 'chunkSizeBytes';
2023
        }
2024
2025 6
        if ($this->isVersioned) {
2026
            $serialized[] = 'isVersioned';
2027
            $serialized[] = 'versionField';
2028
        }
2029
2030 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...
2031
            $serialized[] = 'lifecycleCallbacks';
2032
        }
2033
2034 6
        if ($this->collectionCapped) {
2035 1
            $serialized[] = 'collectionCapped';
2036 1
            $serialized[] = 'collectionSize';
2037 1
            $serialized[] = 'collectionMax';
2038
        }
2039
2040 6
        if ($this->isReadOnly) {
2041
            $serialized[] = 'isReadOnly';
2042
        }
2043
2044 6
        return $serialized;
2045
    }
2046
2047
    /**
2048
     * Restores some state that can not be serialized/unserialized.
2049
     */
2050 6
    public function __wakeup()
2051
    {
2052
        // Restore ReflectionClass and properties
2053 6
        $this->reflClass    = new ReflectionClass($this->name);
2054 6
        $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...\InstantiatorInterface> 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...
2055
2056 6
        foreach ($this->fieldMappings as $field => $mapping) {
2057 3
            if (isset($mapping['declared'])) {
2058 1
                $reflField = new ReflectionProperty($mapping['declared'], $field);
2059
            } else {
2060 3
                $reflField = $this->reflClass->getProperty($field);
2061
            }
2062 3
            $reflField->setAccessible(true);
2063 3
            $this->reflFields[$field] = $reflField;
2064
        }
2065 6
    }
2066
2067
    /**
2068
     * Creates a new instance of the mapped class, without invoking the constructor.
2069
     */
2070 370
    public function newInstance() : object
2071
    {
2072 370
        return $this->instantiator->instantiate($this->name);
2073
    }
2074
2075 130
    private function isAllowedGridFSField(string $name) : bool
2076
    {
2077 130
        return in_array($name, self::ALLOWED_GRIDFS_FIELDS, true);
2078
    }
2079
2080 1570
    private function checkDuplicateMapping(array $mapping) : void
2081
    {
2082 1570
        if ($mapping['notSaved'] ?? false) {
2083 959
            return;
2084
        }
2085
2086 1570
        foreach ($this->fieldMappings as $fieldName => $otherMapping) {
2087
            // Ignore fields with the same name - we can safely override their mapping
2088 1518
            if ($mapping['fieldName'] === $fieldName) {
2089 120
                continue;
2090
            }
2091
2092
            // Ignore fields with a different name in the database
2093 1514
            if ($mapping['name'] !== $otherMapping['name']) {
2094 1514
                continue;
2095
            }
2096
2097
            // If the other field is not saved, ignore it as well
2098 2
            if ($otherMapping['notSaved'] ?? false) {
2099
                continue;
2100
            }
2101
2102 2
            throw MappingException::duplicateDatabaseFieldName($this->getName(), $mapping['fieldName'], $mapping['name'], $fieldName);
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
2103
        }
2104 1570
    }
2105
}
2106