Completed
Push — master ( ecb96f...9f19a4 )
by Andreas
21s queued 13s
created

ClassMetadata::invokeLifecycleCallbacks()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 7.8984

Importance

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