Completed
Pull Request — master (#1812)
by Catalin
15:19
created

ClassMetadata   F

Complexity

Total Complexity 280

Size/Duplication

Total Lines 2123
Duplicated Lines 0 %

Coupling/Cohesion

Components 9
Dependencies 4

Test Coverage

Coverage 85.77%

Importance

Changes 0
Metric Value
wmc 280
lcom 9
cbo 4
dl 0
loc 2123
ccs 482
cts 562
cp 0.8577
rs 0.6314
c 0
b 0
f 0

111 Methods

Rating   Name   Duplication   Size   Complexity  
A __wakeup() 0 16 4
A newInstance() 0 4 1
A getReferenceId() 0 4 2
A getReferencePrefix() 0 8 3
A getReferenceFieldName() 0 8 3
A getReflectionClass() 0 8 2
A isIdentifier() 0 4 1
A setIdentifier() 0 4 1
A getIdentifier() 0 4 1
A getIdentifierFieldNames() 0 4 1
A hasField() 0 4 1
A setInheritanceType() 0 4 1
A isInheritedField() 0 4 1
A getShardKey() 0 4 1
A isSharded() 0 4 2
A setReadPreference() 0 5 1
A setWriteConcern() 0 4 1
A getWriteConcern() 0 4 1
A hasWriteConcern() 0 4 1
A setChangeTrackingPolicy() 0 4 1
A isChangeTrackingDeferredExplicit() 0 4 1
A isChangeTrackingDeferredImplicit() 0 4 1
A isChangeTrackingNotify() 0 4 1
A getReflectionProperties() 0 4 1
A getReflectionProperty() 0 4 1
A getName() 0 4 1
A __construct() 0 8 1
A setCustomRepositoryClass() 0 8 3
B invokeLifecycleCallbacks() 0 18 5
A hasLifecycleCallbacks() 0 4 1
A getLifecycleCallbacks() 0 4 1
A addLifecycleCallback() 0 8 3
A setLifecycleCallbacks() 0 4 1
A registerAlsoLoadMethod() 0 4 2
A setAlsoLoadMethods() 0 4 1
C setDiscriminatorField() 0 25 7
B setDiscriminatorMap() 0 16 5
A setDefaultDiscriminatorValue() 0 14 3
A setDiscriminatorValue() 0 5 1
B addIndex() 0 22 6
A getIndexes() 0 4 1
A hasIndexes() 0 4 2
C setShardKey() 0 44 13
A getDatabase() 0 4 1
A setDatabase() 0 4 1
A getCollection() 0 4 1
A setCollection() 0 14 3
A getCollectionCapped() 0 4 1
A setCollectionCapped() 0 4 1
A getCollectionSize() 0 4 1
A setCollectionSize() 0 4 1
A getCollectionMax() 0 4 1
A setCollectionMax() 0 4 1
A isMappedToCollection() 0 4 2
C applyStorageStrategy() 0 43 13
A mapOneEmbedded() 0 6 1
A mapManyEmbedded() 0 6 1
A mapOneReference() 0 6 1
A mapManyReference() 0 6 1
A addInheritedFieldMapping() 0 10 2
A addInheritedAssociationMapping() 0 4 1
A hasReference() 0 4 1
A hasEmbed() 0 4 1
A hasAssociation() 0 4 2
A isSingleValuedAssociation() 0 4 2
A isCollectionValuedAssociation() 0 4 2
A isSingleValuedReference() 0 5 2
A isCollectionValuedReference() 0 5 2
A isSingleValuedEmbed() 0 5 2
A isCollectionValuedEmbed() 0 5 2
A setIdGenerator() 0 4 1
A getPHPIdentifierValue() 0 5 1
A getDatabaseIdentifierValue() 0 5 1
A setIdentifierValue() 0 5 1
A getIdentifierValue() 0 4 1
A getIdentifierValues() 0 4 1
A getIdentifierObject() 0 4 1
A setFieldValue() 0 10 3
A getFieldValue() 0 8 4
A getFieldMapping() 0 7 2
A getEmbeddedFieldsMappings() 0 9 1
A getFieldMappingByDbFieldName() 0 10 3
A isNullable() 0 8 3
A hasDiscriminator() 0 4 1
A setIdGeneratorType() 0 4 1
A setIdGeneratorOptions() 0 4 1
A isInheritanceTypeNone() 0 4 1
A isInheritanceTypeSingleCollection() 0 4 1
A isInheritanceTypeCollectionPerClass() 0 4 1
A setSubclasses() 0 6 2
A setParentClasses() 0 10 2
A isIdGeneratorAuto() 0 4 1
A isIdGeneratorIncrement() 0 4 1
A isIdGeneratorUuid() 0 4 1
A isIdGeneratorNone() 0 4 1
A setVersionMapping() 0 9 3
A setVersioned() 0 4 1
A setVersionField() 0 4 1
A setLockMapping() 0 9 2
A setLockable() 0 4 1
A setLockField() 0 4 1
A markReadOnly() 0 4 1
A getFieldNames() 0 4 1
A getAssociationNames() 0 4 1
A getTypeOfField() 0 5 2
A getAssociationTargetClass() 0 8 2
A getAssociationCollectionClass() 0 12 3
A isAssociationInverseSide() 0 4 1
A getAssociationMappedByTargetField() 0 4 1
F mapField() 0 162 68
F __sleep() 0 73 12

How to fix   Complexity   

Complex Class

Complex classes like ClassMetadata often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ClassMetadata, and based on these observations, apply Extract Interface, too.

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

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

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

Loading history...
480 1482
    }
481
482
    /**
483
     * Helper method to get reference id of ref* type references
484
     * @param mixed  $reference
485
     * @param string $storeAs
486
     * @return mixed
487
     * @internal
488
     */
489 121
    public static function getReferenceId($reference, $storeAs)
490
    {
491 121
        return $storeAs === self::REFERENCE_STORE_AS_ID ? $reference : $reference[self::getReferencePrefix($storeAs) . 'id'];
492
    }
493
494
    /**
495
     * Returns the reference prefix used for a reference
496
     * @param string $storeAs
497
     * @return string
498
     */
499 186
    private static function getReferencePrefix($storeAs)
500
    {
501 186
        if (! in_array($storeAs, [self::REFERENCE_STORE_AS_REF, self::REFERENCE_STORE_AS_DB_REF, self::REFERENCE_STORE_AS_DB_REF_WITH_DB])) {
502
            throw new \LogicException('Can only get a reference prefix for DBRef and reference arrays');
503
        }
504
505 186
        return $storeAs === self::REFERENCE_STORE_AS_REF ? '' : '$';
506
    }
507
508
    /**
509
     * Returns a fully qualified field name for a given reference
510
     * @param string $storeAs
511
     * @param string $pathPrefix The field path prefix
512
     * @return string
513
     * @internal
514
     */
515 134
    public static function getReferenceFieldName($storeAs, $pathPrefix = '')
516
    {
517 134
        if ($storeAs === self::REFERENCE_STORE_AS_ID) {
518 94
            return $pathPrefix;
519
        }
520
521 122
        return ($pathPrefix ? $pathPrefix . '.' : '') . static::getReferencePrefix($storeAs) . 'id';
0 ignored issues
show
Bug introduced by
Since getReferencePrefix() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getReferencePrefix() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
522
    }
523
524
    /**
525
     * {@inheritDoc}
526
     */
527 1370
    public function getReflectionClass()
528
    {
529 1370
        if (! $this->reflClass) {
530
            $this->reflClass = new \ReflectionClass($this->name);
531
        }
532
533 1370
        return $this->reflClass;
534
    }
535
536
    /**
537
     * {@inheritDoc}
538
     */
539 324
    public function isIdentifier($fieldName)
540
    {
541 324
        return $this->identifier === $fieldName;
542
    }
543
544
    /**
545
     * INTERNAL:
546
     * Sets the mapped identifier field of this class.
547
     *
548
     * @param string $identifier
549
     */
550 888
    public function setIdentifier($identifier)
551
    {
552 888
        $this->identifier = $identifier;
553 888
    }
554
555
    /**
556
     * {@inheritDoc}
557
     *
558
     * Since MongoDB only allows exactly one identifier field
559
     * this will always return an array with only one value
560
     */
561 39
    public function getIdentifier()
562
    {
563 39
        return [$this->identifier];
564
    }
565
566
    /**
567
     * {@inheritDoc}
568
     *
569
     * Since MongoDB only allows exactly one identifier field
570
     * this will always return an array with only one value
571
     */
572 98
    public function getIdentifierFieldNames()
573
    {
574 98
        return [$this->identifier];
575
    }
576
577
    /**
578
     * {@inheritDoc}
579
     */
580 891
    public function hasField($fieldName)
581
    {
582 891
        return isset($this->fieldMappings[$fieldName]);
583
    }
584
585
    /**
586
     * Sets the inheritance type used by the class and it's subclasses.
587
     *
588
     * @param int $type
589
     */
590 904
    public function setInheritanceType($type)
591
    {
592 904
        $this->inheritanceType = $type;
593 904
    }
594
595
    /**
596
     * Checks whether a mapped field is inherited from an entity superclass.
597
     *
598
     * @param  string $fieldName
599
     *
600
     * @return bool TRUE if the field is inherited, FALSE otherwise.
601
     */
602 1366
    public function isInheritedField($fieldName)
603
    {
604 1366
        return isset($this->fieldMappings[$fieldName]['inherited']);
605
    }
606
607
    /**
608
     * Registers a custom repository class for the document class.
609
     *
610
     * @param string $repositoryClassName The class name of the custom repository.
611
     */
612 836
    public function setCustomRepositoryClass($repositoryClassName)
613
    {
614 836
        if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
615
            return;
616
        }
617
618 836
        $this->customRepositoryClassName = $repositoryClassName;
619 836
    }
620
621
    /**
622
     * Dispatches the lifecycle event of the given document by invoking all
623
     * registered callbacks.
624
     *
625
     * @param string $event     Lifecycle event
626
     * @param object $document  Document on which the event occurred
627
     * @param array  $arguments Arguments to pass to all callbacks
628
     * @throws \InvalidArgumentException If document class is not this class or
629
     *                                   a Proxy of this class.
630
     */
631 602
    public function invokeLifecycleCallbacks($event, $document, ?array $arguments = null)
632
    {
633 602
        if (! $document instanceof $this->name) {
634 1
            throw new \InvalidArgumentException(sprintf('Expected document class "%s"; found: "%s"', $this->name, get_class($document)));
635
        }
636
637 601
        if (empty($this->lifecycleCallbacks[$event])) {
638 586
            return;
639
        }
640
641 177
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
642 177
            if ($arguments !== null) {
643 176
                call_user_func_array([$document, $callback], $arguments);
644
            } else {
645 177
                $document->$callback();
646
            }
647
        }
648 177
    }
649
650
    /**
651
     * Checks whether the class has callbacks registered for a lifecycle event.
652
     *
653
     * @param string $event Lifecycle event
654
     *
655
     * @return bool
656
     */
657
    public function hasLifecycleCallbacks($event)
658
    {
659
        return ! empty($this->lifecycleCallbacks[$event]);
660
    }
661
662
    /**
663
     * Gets the registered lifecycle callbacks for an event.
664
     *
665
     * @param string $event
666
     * @return array
667
     */
668
    public function getLifecycleCallbacks($event)
669
    {
670
        return $this->lifecycleCallbacks[$event] ?? [];
671
    }
672
673
    /**
674
     * Adds a lifecycle callback for documents of this class.
675
     *
676
     * If the callback is already registered, this is a NOOP.
677
     *
678
     * @param string $callback
679
     * @param string $event
680
     */
681 803
    public function addLifecycleCallback($callback, $event)
682
    {
683 803
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
684 1
            return;
685
        }
686
687 803
        $this->lifecycleCallbacks[$event][] = $callback;
688 803
    }
689
690
    /**
691
     * Sets the lifecycle callbacks for documents of this class.
692
     *
693
     * Any previously registered callbacks are overwritten.
694
     *
695
     * @param array $callbacks
696
     */
697 887
    public function setLifecycleCallbacks(array $callbacks)
698
    {
699 887
        $this->lifecycleCallbacks = $callbacks;
700 887
    }
701
702
    /**
703
     * Registers a method for loading document data before field hydration.
704
     *
705
     * Note: A method may be registered multiple times for different fields.
706
     * it will be invoked only once for the first field found.
707
     *
708
     * @param string       $method Method name
709
     * @param array|string $fields Database field name(s)
710
     */
711 14
    public function registerAlsoLoadMethod($method, $fields)
712
    {
713 14
        $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : [$fields];
714 14
    }
715
716
    /**
717
     * Sets the AlsoLoad methods for documents of this class.
718
     *
719
     * Any previously registered methods are overwritten.
720
     *
721
     * @param array $methods
722
     */
723 887
    public function setAlsoLoadMethods(array $methods)
724
    {
725 887
        $this->alsoLoadMethods = $methods;
726 887
    }
727
728
    /**
729
     * Sets the discriminator field.
730
     *
731
     * The field name is the the unmapped database field. Discriminator values
732
     * are only used to discern the hydration class and are not mapped to class
733
     * properties.
734
     *
735
     * @param string $discriminatorField
736
     *
737
     * @throws MappingException If the discriminator field conflicts with the
738
     *                          "name" attribute of a mapped field.
739
     */
740 913
    public function setDiscriminatorField($discriminatorField)
741
    {
742 913
        if ($discriminatorField === null) {
743 845
            $this->discriminatorField = null;
744
745 845
            return;
746
        }
747
748
        // Handle array argument with name/fieldName keys for BC
749 117
        if (is_array($discriminatorField)) {
750
            if (isset($discriminatorField['name'])) {
751
                $discriminatorField = $discriminatorField['name'];
752
            } elseif (isset($discriminatorField['fieldName'])) {
753
                $discriminatorField = $discriminatorField['fieldName'];
754
            }
755
        }
756
757 117
        foreach ($this->fieldMappings as $fieldMapping) {
758 4
            if ($discriminatorField === $fieldMapping['name']) {
759 4
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
760
            }
761
        }
762
763 116
        $this->discriminatorField = $discriminatorField;
764 116
    }
765
766
    /**
767
     * Sets the discriminator values used by this class.
768
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
769
     *
770
     * @param array $map
771
     *
772
     * @throws MappingException
773
     */
774 906
    public function setDiscriminatorMap(array $map)
775
    {
776 906
        foreach ($map as $value => $className) {
777 112
            $this->discriminatorMap[$value] = $className;
778 112
            if ($this->name === $className) {
779 104
                $this->discriminatorValue = $value;
780
            } else {
781 111
                if (! class_exists($className)) {
782
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
783
                }
784 111
                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...
785 112
                    $this->subClasses[] = $className;
786
                }
787
            }
788
        }
789 906
    }
790
791
    /**
792
     * Sets the default discriminator value to be used for this class
793
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies if the document has no discriminator value
794
     *
795
     * @param string $defaultDiscriminatorValue
796
     *
797
     * @throws MappingException
798
     */
799 890
    public function setDefaultDiscriminatorValue($defaultDiscriminatorValue)
800
    {
801 890
        if ($defaultDiscriminatorValue === null) {
802 887
            $this->defaultDiscriminatorValue = null;
803
804 887
            return;
805
        }
806
807 48
        if (! array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
808
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
809
        }
810
811 48
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
812 48
    }
813
814
    /**
815
     * Sets the discriminator value for this class.
816
     * Used for JOINED/SINGLE_TABLE inheritance and multiple document types in a single
817
     * collection.
818
     *
819
     * @param string $value
820
     */
821 3
    public function setDiscriminatorValue($value)
822
    {
823 3
        $this->discriminatorMap[$value] = $this->name;
824 3
        $this->discriminatorValue = $value;
825 3
    }
826
827
    /**
828
     * Add a index for this Document.
829
     *
830
     * @param array $keys    Array of keys for the index.
831
     * @param array $options Array of options for the index.
832
     */
833 183
    public function addIndex($keys, array $options = [])
834
    {
835 183
        $this->indexes[] = [
836
            'keys' => array_map(function ($value) {
837 183
                if ($value === 1 || $value === -1) {
838 45
                    return (int) $value;
839
                }
840 183
                if (is_string($value)) {
841 183
                    $lower = strtolower($value);
842 183
                    if ($lower === 'asc') {
843 176
                        return 1;
844
                    }
845
846 52
                    if ($lower === 'desc') {
847
                        return -1;
848
                    }
849
                }
850 52
                return $value;
851 183
            }, $keys),
852 183
            'options' => $options,
853
        ];
854 183
    }
855
856
    /**
857
     * Returns the array of indexes for this Document.
858
     *
859
     * @return array $indexes The array of indexes.
860
     */
861 23
    public function getIndexes()
862
    {
863 23
        return $this->indexes;
864
    }
865
866
    /**
867
     * Checks whether this document has indexes or not.
868
     *
869
     * @return bool
870
     */
871
    public function hasIndexes()
872
    {
873
        return $this->indexes ? true : false;
874
    }
875
876
    /**
877
     * Set shard key for this Document.
878
     *
879
     * @param array $keys    Array of document keys.
880
     * @param array $options Array of sharding options.
881
     *
882
     * @throws MappingException
883
     */
884 71
    public function setShardKey(array $keys, array $options = [])
885
    {
886 71
        if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && $this->shardKey !== null) {
887 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...
888
        }
889
890 71
        if ($this->isEmbeddedDocument) {
891 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...
892
        }
893
894 69
        foreach (array_keys($keys) as $field) {
895 69
            if (! isset($this->fieldMappings[$field])) {
896 62
                continue;
897
            }
898
899 7
            if (in_array($this->fieldMappings[$field]['type'], ['many', 'collection'])) {
900 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...
901
            }
902
903 4
            if ($this->fieldMappings[$field]['strategy'] !== static::STORAGE_STRATEGY_SET) {
904 4
                throw MappingException::onlySetStrategyAllowedInShardKey($this->getName(), $field);
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
905
            }
906
        }
907
908 65
        $this->shardKey = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array('keys' => \array_m... 'options' => $options) of type array<string,array,{"key...ay","options":"array"}> is incompatible with the declared type string|null of property $shardKey.

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

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

Loading history...
909
            'keys' => array_map(function ($value) {
910 65
                if ($value === 1 || $value === -1) {
911 5
                    return (int) $value;
912
                }
913 65
                if (is_string($value)) {
914 65
                    $lower = strtolower($value);
915 65
                    if ($lower === 'asc') {
916 63
                        return 1;
917
                    }
918
919 47
                    if ($lower === 'desc') {
920
                        return -1;
921
                    }
922
                }
923 47
                return $value;
924 65
            }, $keys),
925 65
            'options' => $options,
926
        ];
927 65
    }
928
929
    /**
930
     * @return array
931
     */
932 17
    public function getShardKey()
933
    {
934 17
        return $this->shardKey;
935
    }
936
937
    /**
938
     * Checks whether this document has shard key or not.
939
     *
940
     * @return bool
941
     */
942 1099
    public function isSharded()
943
    {
944 1099
        return $this->shardKey ? true : false;
945
    }
946
947
    /**
948
     * Sets the read preference used by this class.
949
     *
950
     * @param string     $readPreference
951
     * @param array|null $tags
952
     */
953 887
    public function setReadPreference($readPreference, $tags)
954
    {
955 887
        $this->readPreference = $readPreference;
956 887
        $this->readPreferenceTags = $tags;
957 887
    }
958
959
    /**
960
     * Sets the write concern used by this class.
961
     *
962
     * @param string $writeConcern
963
     */
964 897
    public function setWriteConcern($writeConcern)
965
    {
966 897
        $this->writeConcern = $writeConcern;
967 897
    }
968
969
    /**
970
     * @return string
971
     */
972 11
    public function getWriteConcern()
973
    {
974 11
        return $this->writeConcern;
975
    }
976
977
    /**
978
     * Whether there is a write concern configured for this class.
979
     *
980
     * @return bool
981
     */
982 551
    public function hasWriteConcern()
983
    {
984 551
        return $this->writeConcern !== null;
985
    }
986
987
    /**
988
     * Sets the change tracking policy used by this class.
989
     *
990
     * @param int $policy
991
     */
992 889
    public function setChangeTrackingPolicy($policy)
993
    {
994 889
        $this->changeTrackingPolicy = $policy;
995 889
    }
996
997
    /**
998
     * Whether the change tracking policy of this class is "deferred explicit".
999
     *
1000
     * @return bool
1001
     */
1002 62
    public function isChangeTrackingDeferredExplicit()
1003
    {
1004 62
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT;
1005
    }
1006
1007
    /**
1008
     * Whether the change tracking policy of this class is "deferred implicit".
1009
     *
1010
     * @return bool
1011
     */
1012 571
    public function isChangeTrackingDeferredImplicit()
1013
    {
1014 571
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT;
1015
    }
1016
1017
    /**
1018
     * Whether the change tracking policy of this class is "notify".
1019
     *
1020
     * @return bool
1021
     */
1022 310
    public function isChangeTrackingNotify()
1023
    {
1024 310
        return $this->changeTrackingPolicy === self::CHANGETRACKING_NOTIFY;
1025
    }
1026
1027
    /**
1028
     * Gets the ReflectionProperties of the mapped class.
1029
     *
1030
     * @return array An array of ReflectionProperty instances.
1031
     */
1032 98
    public function getReflectionProperties()
1033
    {
1034 98
        return $this->reflFields;
1035
    }
1036
1037
    /**
1038
     * Gets a ReflectionProperty for a specific field of the mapped class.
1039
     *
1040
     * @param string $name
1041
     *
1042
     * @return \ReflectionProperty
1043
     */
1044
    public function getReflectionProperty($name)
1045
    {
1046
        return $this->reflFields[$name];
1047
    }
1048
1049
    /**
1050
     * {@inheritDoc}
1051
     */
1052 1375
    public function getName()
1053
    {
1054 1375
        return $this->name;
1055
    }
1056
1057
    /**
1058
     * Returns the database this Document is mapped to.
1059
     *
1060
     * @return string $db The database name.
1061
     */
1062 1299
    public function getDatabase()
1063
    {
1064 1299
        return $this->db;
1065
    }
1066
1067
    /**
1068
     * Set the database this Document is mapped to.
1069
     *
1070
     * @param string $db The database name
1071
     */
1072 92
    public function setDatabase($db)
1073
    {
1074 92
        $this->db = $db;
1075 92
    }
1076
1077
    /**
1078
     * Get the collection this Document is mapped to.
1079
     *
1080
     * @return string $collection The collection name.
1081
     */
1082 1300
    public function getCollection()
1083
    {
1084 1300
        return $this->collection;
1085
    }
1086
1087
    /**
1088
     * Sets the collection this Document is mapped to.
1089
     *
1090
     * @param array|string $name
1091
     *
1092
     * @throws \InvalidArgumentException
1093
     */
1094 1482
    public function setCollection($name)
1095
    {
1096 1482
        if (is_array($name)) {
1097 1
            if (! isset($name['name'])) {
1098
                throw new \InvalidArgumentException('A name key is required when passing an array to setCollection()');
1099
            }
1100 1
            $this->collectionCapped = $name['capped'] ?? false;
1101 1
            $this->collectionSize = $name['size'] ?? 0;
1102 1
            $this->collectionMax = $name['max'] ?? 0;
1103 1
            $this->collection = $name['name'];
1104
        } else {
1105 1482
            $this->collection = $name;
1106
        }
1107 1482
    }
1108
1109
    /**
1110
     * Get whether or not the documents collection is capped.
1111
     *
1112
     * @return bool
1113
     */
1114 5
    public function getCollectionCapped()
1115
    {
1116 5
        return $this->collectionCapped;
1117
    }
1118
1119
    /**
1120
     * Set whether or not the documents collection is capped.
1121
     *
1122
     * @param bool $bool
1123
     */
1124 1
    public function setCollectionCapped($bool)
1125
    {
1126 1
        $this->collectionCapped = $bool;
1127 1
    }
1128
1129
    /**
1130
     * Get the collection size
1131
     *
1132
     * @return int
1133
     */
1134 5
    public function getCollectionSize()
1135
    {
1136 5
        return $this->collectionSize;
1137
    }
1138
1139
    /**
1140
     * Set the collection size.
1141
     *
1142
     * @param int $size
1143
     */
1144 1
    public function setCollectionSize($size)
1145
    {
1146 1
        $this->collectionSize = $size;
1147 1
    }
1148
1149
    /**
1150
     * Get the collection max.
1151
     *
1152
     * @return int
1153
     */
1154 5
    public function getCollectionMax()
1155
    {
1156 5
        return $this->collectionMax;
1157
    }
1158
1159
    /**
1160
     * Set the collection max.
1161
     *
1162
     * @param int $max
1163
     */
1164 1
    public function setCollectionMax($max)
1165
    {
1166 1
        $this->collectionMax = $max;
1167 1
    }
1168
1169
    /**
1170
     * Returns TRUE if this Document is mapped to a collection FALSE otherwise.
1171
     *
1172
     * @return bool
1173
     */
1174
    public function isMappedToCollection()
1175
    {
1176
        return $this->collection ? true : false;
1177
    }
1178
1179
    /**
1180
     * Validates the storage strategy of a mapping for consistency
1181
     * @param array $mapping
1182
     * @throws MappingException
1183
     */
1184 1392
    private function applyStorageStrategy(array &$mapping)
1185
    {
1186 1392
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1187 1374
            return;
1188
        }
1189
1190
        switch (true) {
1191 1357
            case $mapping['type'] === 'int':
1192 1356
            case $mapping['type'] === 'float':
1193 820
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1194 820
                $allowedStrategies = [self::STORAGE_STRATEGY_SET, self::STORAGE_STRATEGY_INCREMENT];
1195 820
                break;
1196
1197 1356
            case $mapping['type'] === 'many':
1198 1080
                $defaultStrategy = CollectionHelper::DEFAULT_STRATEGY;
1199
                $allowedStrategies = [
1200 1080
                    self::STORAGE_STRATEGY_PUSH_ALL,
1201 1080
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1202 1080
                    self::STORAGE_STRATEGY_SET,
1203 1080
                    self::STORAGE_STRATEGY_SET_ARRAY,
1204 1080
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1205 1080
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1206
                ];
1207 1080
                break;
1208
1209
            default:
1210 1343
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1211 1343
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1212
        }
1213
1214 1357
        if (! isset($mapping['strategy'])) {
1215 1348
            $mapping['strategy'] = $defaultStrategy;
1216
        }
1217
1218 1357
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1219
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1220
        }
1221
1222 1357
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1223 1357
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1224 1
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1225
        }
1226 1356
    }
1227
1228
    /**
1229
     * Map a single embedded document.
1230
     *
1231
     * @param array $mapping The mapping information.
1232
     */
1233 6
    public function mapOneEmbedded(array $mapping)
1234
    {
1235 6
        $mapping['embedded'] = true;
1236 6
        $mapping['type'] = 'one';
1237 6
        $this->mapField($mapping);
1238 5
    }
1239
1240
    /**
1241
     * Map a collection of embedded documents.
1242
     *
1243
     * @param array $mapping The mapping information.
1244
     */
1245 5
    public function mapManyEmbedded(array $mapping)
1246
    {
1247 5
        $mapping['embedded'] = true;
1248 5
        $mapping['type'] = 'many';
1249 5
        $this->mapField($mapping);
1250 5
    }
1251
1252
    /**
1253
     * Map a single document reference.
1254
     *
1255
     * @param array $mapping The mapping information.
1256
     */
1257 2
    public function mapOneReference(array $mapping)
1258
    {
1259 2
        $mapping['reference'] = true;
1260 2
        $mapping['type'] = 'one';
1261 2
        $this->mapField($mapping);
1262 2
    }
1263
1264
    /**
1265
     * Map a collection of document references.
1266
     *
1267
     * @param array $mapping The mapping information.
1268
     */
1269 1
    public function mapManyReference(array $mapping)
1270
    {
1271 1
        $mapping['reference'] = true;
1272 1
        $mapping['type'] = 'many';
1273 1
        $this->mapField($mapping);
1274 1
    }
1275
1276
    /**
1277
     * INTERNAL:
1278
     * Adds a field mapping without completing/validating it.
1279
     * This is mainly used to add inherited field mappings to derived classes.
1280
     *
1281
     * @param array $fieldMapping
1282
     */
1283 116
    public function addInheritedFieldMapping(array $fieldMapping)
1284
    {
1285 116
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1286
1287 116
        if (! isset($fieldMapping['association'])) {
1288 116
            return;
1289
        }
1290
1291 67
        $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1292 67
    }
1293
1294
    /**
1295
     * INTERNAL:
1296
     * Adds an association mapping without completing/validating it.
1297
     * This is mainly used to add inherited association mappings to derived classes.
1298
     *
1299
     * @param array $mapping
1300
     *
1301
     *
1302
     * @throws MappingException
1303
     */
1304 68
    public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
1305
    {
1306 68
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1307 68
    }
1308
1309
    /**
1310
     * Checks whether the class has a mapped association with the given field name.
1311
     *
1312
     * @param string $fieldName
1313
     * @return bool
1314
     */
1315 32
    public function hasReference($fieldName)
1316
    {
1317 32
        return isset($this->fieldMappings[$fieldName]['reference']);
1318
    }
1319
1320
    /**
1321
     * Checks whether the class has a mapped embed with the given field name.
1322
     *
1323
     * @param string $fieldName
1324
     * @return bool
1325
     */
1326 5
    public function hasEmbed($fieldName)
1327
    {
1328 5
        return isset($this->fieldMappings[$fieldName]['embedded']);
1329
    }
1330
1331
    /**
1332
     * {@inheritDoc}
1333
     *
1334
     * Checks whether the class has a mapped association (embed or reference) with the given field name.
1335
     */
1336 7
    public function hasAssociation($fieldName)
1337
    {
1338 7
        return $this->hasReference($fieldName) || $this->hasEmbed($fieldName);
1339
    }
1340
1341
    /**
1342
     * {@inheritDoc}
1343
     *
1344
     * Checks whether the class has a mapped reference or embed for the specified field and
1345
     * is a single valued association.
1346
     */
1347
    public function isSingleValuedAssociation($fieldName)
1348
    {
1349
        return $this->isSingleValuedReference($fieldName) || $this->isSingleValuedEmbed($fieldName);
1350
    }
1351
1352
    /**
1353
     * {@inheritDoc}
1354
     *
1355
     * Checks whether the class has a mapped reference or embed for the specified field and
1356
     * is a collection valued association.
1357
     */
1358
    public function isCollectionValuedAssociation($fieldName)
1359
    {
1360
        return $this->isCollectionValuedReference($fieldName) || $this->isCollectionValuedEmbed($fieldName);
1361
    }
1362
1363
    /**
1364
     * Checks whether the class has a mapped association for the specified field
1365
     * and if yes, checks whether it is a single-valued association (to-one).
1366
     *
1367
     * @param string $fieldName
1368
     * @return bool TRUE if the association exists and is single-valued, FALSE otherwise.
1369
     */
1370
    public function isSingleValuedReference($fieldName)
1371
    {
1372
        return isset($this->fieldMappings[$fieldName]['association']) &&
1373
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_ONE;
1374
    }
1375
1376
    /**
1377
     * Checks whether the class has a mapped association for the specified field
1378
     * and if yes, checks whether it is a collection-valued association (to-many).
1379
     *
1380
     * @param string $fieldName
1381
     * @return bool TRUE if the association exists and is collection-valued, FALSE otherwise.
1382
     */
1383
    public function isCollectionValuedReference($fieldName)
1384
    {
1385
        return isset($this->fieldMappings[$fieldName]['association']) &&
1386
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_MANY;
1387
    }
1388
1389
    /**
1390
     * Checks whether the class has a mapped embedded document for the specified field
1391
     * and if yes, checks whether it is a single-valued association (to-one).
1392
     *
1393
     * @param string $fieldName
1394
     * @return bool TRUE if the association exists and is single-valued, FALSE otherwise.
1395
     */
1396
    public function isSingleValuedEmbed($fieldName)
1397
    {
1398
        return isset($this->fieldMappings[$fieldName]['association']) &&
1399
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_ONE;
1400
    }
1401
1402
    /**
1403
     * Checks whether the class has a mapped embedded document for the specified field
1404
     * and if yes, checks whether it is a collection-valued association (to-many).
1405
     *
1406
     * @param string $fieldName
1407
     * @return bool TRUE if the association exists and is collection-valued, FALSE otherwise.
1408
     */
1409
    public function isCollectionValuedEmbed($fieldName)
1410
    {
1411
        return isset($this->fieldMappings[$fieldName]['association']) &&
1412
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_MANY;
1413
    }
1414
1415
    /**
1416
     * Sets the ID generator used to generate IDs for instances of this class.
1417
     *
1418
     * @param AbstractIdGenerator $generator
1419
     */
1420 1311
    public function setIdGenerator($generator)
1421
    {
1422 1311
        $this->idGenerator = $generator;
1423 1311
    }
1424
1425
    /**
1426
     * Casts the identifier to its portable PHP type.
1427
     *
1428
     * @param mixed $id
1429
     * @return mixed $id
1430
     */
1431 594
    public function getPHPIdentifierValue($id)
1432
    {
1433 594
        $idType = $this->fieldMappings[$this->identifier]['type'];
1434 594
        return Type::getType($idType)->convertToPHPValue($id);
1435
    }
1436
1437
    /**
1438
     * Casts the identifier to its database type.
1439
     *
1440
     * @param mixed $id
1441
     * @return mixed $id
1442
     */
1443 657
    public function getDatabaseIdentifierValue($id)
1444
    {
1445 657
        $idType = $this->fieldMappings[$this->identifier]['type'];
1446 657
        return Type::getType($idType)->convertToDatabaseValue($id);
1447
    }
1448
1449
    /**
1450
     * Sets the document identifier of a document.
1451
     *
1452
     * The value will be converted to a PHP type before being set.
1453
     *
1454
     * @param object $document
1455
     * @param mixed  $id
1456
     */
1457 523
    public function setIdentifierValue($document, $id)
1458
    {
1459 523
        $id = $this->getPHPIdentifierValue($id);
1460 523
        $this->reflFields[$this->identifier]->setValue($document, $id);
1461 523
    }
1462
1463
    /**
1464
     * Gets the document identifier as a PHP type.
1465
     *
1466
     * @param object $document
1467
     * @return mixed $id
1468
     */
1469 606
    public function getIdentifierValue($document)
1470
    {
1471 606
        return $this->reflFields[$this->identifier]->getValue($document);
1472
    }
1473
1474
    /**
1475
     * {@inheritDoc}
1476
     *
1477
     * Since MongoDB only allows exactly one identifier field this is a proxy
1478
     * to {@see getIdentifierValue()} and returns an array with the identifier
1479
     * field as a key.
1480
     */
1481
    public function getIdentifierValues($object)
1482
    {
1483
        return [$this->identifier => $this->getIdentifierValue($object)];
1484
    }
1485
1486
    /**
1487
     * Get the document identifier object as a database type.
1488
     *
1489
     * @param object $document
1490
     *
1491
     * @return ObjectId $id The ObjectId
1492
     */
1493 31
    public function getIdentifierObject($document)
1494
    {
1495 31
        return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));
1496
    }
1497
1498
    /**
1499
     * Sets the specified field to the specified value on the given document.
1500
     *
1501
     * @param object $document
1502
     * @param string $field
1503
     * @param mixed  $value
1504
     */
1505 8
    public function setFieldValue($document, $field, $value)
1506
    {
1507 8
        if ($document instanceof Proxy && ! $document->__isInitialized()) {
1508
            //property changes to an uninitialized proxy will not be tracked or persisted,
1509
            //so the proxy needs to be loaded first.
1510 1
            $document->__load();
1511
        }
1512
1513 8
        $this->reflFields[$field]->setValue($document, $value);
1514 8
    }
1515
1516
    /**
1517
     * Gets the specified field's value off the given document.
1518
     *
1519
     * @param object $document
1520
     * @param string $field
1521
     *
1522
     * @return mixed
1523
     */
1524 27
    public function getFieldValue($document, $field)
1525
    {
1526 27
        if ($document instanceof Proxy && $field !== $this->identifier && ! $document->__isInitialized()) {
1527 1
            $document->__load();
1528
        }
1529
1530 27
        return $this->reflFields[$field]->getValue($document);
1531
    }
1532
1533
    /**
1534
     * Gets the mapping of a field.
1535
     *
1536
     * @param string $fieldName The field name.
1537
     *
1538
     * @return array  The field mapping.
1539
     *
1540
     * @throws MappingException If the $fieldName is not found in the fieldMappings array.
1541
     */
1542 169
    public function getFieldMapping($fieldName)
1543
    {
1544 169
        if (! isset($this->fieldMappings[$fieldName])) {
1545 6
            throw MappingException::mappingNotFound($this->name, $fieldName);
1546
        }
1547 167
        return $this->fieldMappings[$fieldName];
1548
    }
1549
1550
    /**
1551
     * Gets mappings of fields holding embedded document(s).
1552
     *
1553
     * @return array of field mappings
1554
     */
1555 562
    public function getEmbeddedFieldsMappings()
1556
    {
1557 562
        return array_filter(
1558 562
            $this->associationMappings,
1559
            function ($assoc) {
1560 427
                return ! empty($assoc['embedded']);
1561 562
            }
1562
        );
1563
    }
1564
1565
    /**
1566
     * Gets the field mapping by its DB name.
1567
     * E.g. it returns identifier's mapping when called with _id.
1568
     *
1569
     * @param string $dbFieldName
1570
     *
1571
     * @return array
1572
     * @throws MappingException
1573
     */
1574 4
    public function getFieldMappingByDbFieldName($dbFieldName)
1575
    {
1576 4
        foreach ($this->fieldMappings as $mapping) {
1577 4
            if ($mapping['name'] === $dbFieldName) {
1578 4
                return $mapping;
1579
            }
1580
        }
1581
1582
        throw MappingException::mappingNotFoundByDbName($this->name, $dbFieldName);
1583
    }
1584
1585
    /**
1586
     * Check if the field is not null.
1587
     *
1588
     * @param string $fieldName The field name
1589
     *
1590
     * @return bool  TRUE if the field is not null, FALSE otherwise.
1591
     */
1592 1
    public function isNullable($fieldName)
1593
    {
1594 1
        $mapping = $this->getFieldMapping($fieldName);
1595 1
        if ($mapping !== false) {
1596 1
            return isset($mapping['nullable']) && $mapping['nullable'] === true;
1597
        }
1598
        return false;
1599
    }
1600
1601
    /**
1602
     * Checks whether the document has a discriminator field and value configured.
1603
     *
1604
     * @return bool
1605
     */
1606 497
    public function hasDiscriminator()
1607
    {
1608 497
        return isset($this->discriminatorField, $this->discriminatorValue);
1609
    }
1610
1611
    /**
1612
     * Sets the type of Id generator to use for the mapped class.
1613
     *
1614
     * @param string $generatorType Generator type.
1615
     */
1616 887
    public function setIdGeneratorType($generatorType)
1617
    {
1618 887
        $this->generatorType = $generatorType;
1619 887
    }
1620
1621
    /**
1622
     * Sets the Id generator options.
1623
     *
1624
     * @param array $generatorOptions Generator options.
1625
     */
1626
    public function setIdGeneratorOptions($generatorOptions)
1627
    {
1628
        $this->generatorOptions = $generatorOptions;
1629
    }
1630
1631
    /**
1632
     * @return bool
1633
     */
1634 569
    public function isInheritanceTypeNone()
1635
    {
1636 569
        return $this->inheritanceType === self::INHERITANCE_TYPE_NONE;
1637
    }
1638
1639
    /**
1640
     * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.
1641
     *
1642
     * @return bool
1643
     */
1644 886
    public function isInheritanceTypeSingleCollection()
1645
    {
1646 886
        return $this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION;
1647
    }
1648
1649
    /**
1650
     * Checks whether the mapped class uses the COLLECTION_PER_CLASS inheritance mapping strategy.
1651
     *
1652
     * @return bool
1653
     */
1654
    public function isInheritanceTypeCollectionPerClass()
1655
    {
1656
        return $this->inheritanceType === self::INHERITANCE_TYPE_COLLECTION_PER_CLASS;
1657
    }
1658
1659
    /**
1660
     * Sets the mapped subclasses of this class.
1661
     *
1662
     * @param string[] $subclasses The names of all mapped subclasses.
1663
     */
1664 2
    public function setSubclasses(array $subclasses)
1665
    {
1666 2
        foreach ($subclasses as $subclass) {
1667 2
            $this->subClasses[] = $subclass;
1668
        }
1669 2
    }
1670
1671
    /**
1672
     * Sets the parent class names.
1673
     * Assumes that the class names in the passed array are in the order:
1674
     * directParent -> directParentParent -> directParentParentParent ... -> root.
1675
     *
1676
     * @param string[] $classNames
1677
     */
1678 1366
    public function setParentClasses(array $classNames)
1679
    {
1680 1366
        $this->parentClasses = $classNames;
1681
1682 1366
        if (count($classNames) <= 0) {
1683 1365
            return;
1684
        }
1685
1686 100
        $this->rootDocumentName = array_pop($classNames);
1687 100
    }
1688
1689
    /**
1690
     * Checks whether the class will generate a new \MongoDB\BSON\ObjectId instance for us.
1691
     *
1692
     * @return bool TRUE if the class uses the AUTO generator, FALSE otherwise.
1693
     */
1694
    public function isIdGeneratorAuto()
1695
    {
1696
        return $this->generatorType === self::GENERATOR_TYPE_AUTO;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->generatorType (string) and self::GENERATOR_TYPE_AUTO (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1697
    }
1698
1699
    /**
1700
     * Checks whether the class will use a collection to generate incremented identifiers.
1701
     *
1702
     * @return bool TRUE if the class uses the INCREMENT generator, FALSE otherwise.
1703
     */
1704
    public function isIdGeneratorIncrement()
1705
    {
1706
        return $this->generatorType === self::GENERATOR_TYPE_INCREMENT;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->generatorType (string) and self::GENERATOR_TYPE_INCREMENT (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1707
    }
1708
1709
    /**
1710
     * Checks whether the class will generate a uuid id.
1711
     *
1712
     * @return bool TRUE if the class uses the UUID generator, FALSE otherwise.
1713
     */
1714
    public function isIdGeneratorUuid()
1715
    {
1716
        return $this->generatorType === self::GENERATOR_TYPE_UUID;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->generatorType (string) and self::GENERATOR_TYPE_UUID (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1717
    }
1718
1719
    /**
1720
     * Checks whether the class uses no id generator.
1721
     *
1722
     * @return bool TRUE if the class does not use any id generator, FALSE otherwise.
1723
     */
1724
    public function isIdGeneratorNone()
1725
    {
1726
        return $this->generatorType === self::GENERATOR_TYPE_NONE;
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $this->generatorType (string) and self::GENERATOR_TYPE_NONE (integer) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1727
    }
1728
1729
    /**
1730
     * Sets the version field mapping used for versioning. Sets the default
1731
     * value to use depending on the column type.
1732
     *
1733
     * @param array $mapping The version field mapping array
1734
     *
1735
     * @throws LockException
1736
     */
1737 67
    public function setVersionMapping(array &$mapping)
1738
    {
1739 67
        if ($mapping['type'] !== 'int' && $mapping['type'] !== 'date') {
1740 1
            throw LockException::invalidVersionFieldType($mapping['type']);
1741
        }
1742
1743 66
        $this->isVersioned  = true;
1744 66
        $this->versionField = $mapping['fieldName'];
1745 66
    }
1746
1747
    /**
1748
     * Sets whether this class is to be versioned for optimistic locking.
1749
     *
1750
     * @param bool $bool
1751
     */
1752 887
    public function setVersioned($bool)
1753
    {
1754 887
        $this->isVersioned = $bool;
1755 887
    }
1756
1757
    /**
1758
     * Sets the name of the field that is to be used for versioning if this class is
1759
     * versioned for optimistic locking.
1760
     *
1761
     * @param string $versionField
1762
     */
1763 887
    public function setVersionField($versionField)
1764
    {
1765 887
        $this->versionField = $versionField;
1766 887
    }
1767
1768
    /**
1769
     * Sets the version field mapping used for versioning. Sets the default
1770
     * value to use depending on the column type.
1771
     *
1772
     * @param array $mapping The version field mapping array
1773
     *
1774
     * @throws LockException
1775
     */
1776 22
    public function setLockMapping(array &$mapping)
1777
    {
1778 22
        if ($mapping['type'] !== 'int') {
1779 1
            throw LockException::invalidLockFieldType($mapping['type']);
1780
        }
1781
1782 21
        $this->isLockable = true;
1783 21
        $this->lockField = $mapping['fieldName'];
1784 21
    }
1785
1786
    /**
1787
     * Sets whether this class is to allow pessimistic locking.
1788
     *
1789
     * @param bool $bool
1790
     */
1791
    public function setLockable($bool)
1792
    {
1793
        $this->isLockable = $bool;
1794
    }
1795
1796
    /**
1797
     * Sets the name of the field that is to be used for storing whether a document
1798
     * is currently locked or not.
1799
     *
1800
     * @param string $lockField
1801
     */
1802
    public function setLockField($lockField)
1803
    {
1804
        $this->lockField = $lockField;
1805
    }
1806
1807
    /**
1808
     * Marks this class as read only, no change tracking is applied to it.
1809
     */
1810 5
    public function markReadOnly()
1811
    {
1812 5
        $this->isReadOnly = true;
1813 5
    }
1814
1815
    /**
1816
     * {@inheritDoc}
1817
     */
1818
    public function getFieldNames()
1819
    {
1820
        return array_keys($this->fieldMappings);
1821
    }
1822
1823
    /**
1824
     * {@inheritDoc}
1825
     */
1826
    public function getAssociationNames()
1827
    {
1828
        return array_keys($this->associationMappings);
1829
    }
1830
1831
    /**
1832
     * {@inheritDoc}
1833
     */
1834 23
    public function getTypeOfField($fieldName)
1835
    {
1836 23
        return isset($this->fieldMappings[$fieldName]) ?
1837 23
            $this->fieldMappings[$fieldName]['type'] : null;
1838
    }
1839
1840
    /**
1841
     * {@inheritDoc}
1842
     */
1843 4
    public function getAssociationTargetClass($assocName)
1844
    {
1845 4
        if (! isset($this->associationMappings[$assocName])) {
1846 2
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1847
        }
1848
1849 2
        return $this->associationMappings[$assocName]['targetDocument'];
1850
    }
1851
1852
    /**
1853
     * Retrieve the collectionClass associated with an association
1854
     *
1855
     * @param string $assocName
1856
     */
1857 1
    public function getAssociationCollectionClass($assocName)
1858
    {
1859 1
        if (! isset($this->associationMappings[$assocName])) {
1860
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
1861
        }
1862
1863 1
        if (! array_key_exists('collectionClass', $this->associationMappings[$assocName])) {
1864
            throw new InvalidArgumentException("collectionClass can only be applied to 'embedMany' and 'referenceMany' associations.");
1865
        }
1866
1867 1
        return $this->associationMappings[$assocName]['collectionClass'];
1868
    }
1869
1870
    /**
1871
     * {@inheritDoc}
1872
     */
1873
    public function isAssociationInverseSide($fieldName)
1874
    {
1875
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1876
    }
1877
1878
    /**
1879
     * {@inheritDoc}
1880
     */
1881
    public function getAssociationMappedByTargetField($fieldName)
1882
    {
1883
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
1884
    }
1885
1886
    /**
1887
     * Map a field.
1888
     *
1889
     * @param array $mapping The mapping information.
1890
     *
1891
     * @return array
1892
     *
1893
     * @throws MappingException
1894
     */
1895 1407
    public function mapField(array $mapping)
1896
    {
1897 1407
        if (! isset($mapping['fieldName']) && isset($mapping['name'])) {
1898 8
            $mapping['fieldName'] = $mapping['name'];
1899
        }
1900 1407
        if (! isset($mapping['fieldName'])) {
1901
            throw MappingException::missingFieldName($this->name);
1902
        }
1903 1407
        if (! isset($mapping['name'])) {
1904 1398
            $mapping['name'] = $mapping['fieldName'];
1905
        }
1906 1407
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1907 1
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, $mapping['name']);
1908
        }
1909 1406
        if ($this->discriminatorField !== null && $this->discriminatorField === $mapping['name']) {
1910 1
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1911
        }
1912 1405
        if (isset($mapping['collectionClass'])) {
1913 53
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1914
        }
1915 1405
        if (! empty($mapping['collectionClass'])) {
1916 53
            $rColl = new \ReflectionClass($mapping['collectionClass']);
1917 53
            if (! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1918 1
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1919
            }
1920
        }
1921
1922 1404
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1923 1
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1924
        }
1925
1926 1403
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : [];
1927
1928 1403
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
1929 1107
            $cascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1930
        }
1931
1932 1403
        if (isset($mapping['embedded'])) {
1933 1067
            unset($mapping['cascade']);
1934 1398
        } elseif (isset($mapping['cascade'])) {
1935 904
            $mapping['cascade'] = $cascades;
1936
        }
1937
1938 1403
        $mapping['isCascadeRemove'] = in_array('remove', $cascades);
1939 1403
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1940 1403
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1941 1403
        $mapping['isCascadeMerge'] = in_array('merge', $cascades);
1942 1403
        $mapping['isCascadeDetach'] = in_array('detach', $cascades);
1943
1944 1403
        if (isset($mapping['id']) && $mapping['id'] === true) {
1945 1372
            $mapping['name'] = '_id';
1946 1372
            $this->identifier = $mapping['fieldName'];
1947 1372
            if (isset($mapping['strategy'])) {
1948 1366
                $this->generatorType = constant(self::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
1949
            }
1950 1372
            $this->generatorOptions = $mapping['options'] ?? [];
1951 1372
            switch ($this->generatorType) {
1952 1372
                case self::GENERATOR_TYPE_AUTO:
1953 1300
                    $mapping['type'] = 'id';
1954 1300
                    break;
1955
                default:
1956 145
                    if (! empty($this->generatorOptions['type'])) {
1957 56
                        $mapping['type'] = $this->generatorOptions['type'];
1958 89
                    } elseif (empty($mapping['type'])) {
1959 77
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
1960
                    }
1961
            }
1962 1372
            unset($this->generatorOptions['type']);
1963
        }
1964
1965 1403
        if (! isset($mapping['nullable'])) {
1966 38
            $mapping['nullable'] = false;
1967
        }
1968
1969 1403
        if (isset($mapping['reference'])
1970 1403
            && isset($mapping['storeAs'])
1971 1403
            && $mapping['storeAs'] === self::REFERENCE_STORE_AS_ID
1972 1403
            && ! isset($mapping['targetDocument'])
1973
        ) {
1974 3
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
1975
        }
1976
1977 1400
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
1978 1400
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
1979 4
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
1980
        }
1981
1982 1396
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && isset($mapping['strategy']) && CollectionHelper::isAtomic($mapping['strategy'])) {
1983 1
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
1984
        }
1985
1986 1395
        if (isset($mapping['repositoryMethod']) && ! (empty($mapping['skip']) && empty($mapping['limit']) && empty($mapping['sort']))) {
1987 3
            throw MappingException::repositoryMethodCanNotBeCombinedWithSkipLimitAndSort($this->name, $mapping['fieldName']);
1988
        }
1989
1990 1392
        if (isset($mapping['reference']) && $mapping['type'] === 'one') {
1991 1000
            $mapping['association'] = self::REFERENCE_ONE;
1992
        }
1993 1392
        if (isset($mapping['reference']) && $mapping['type'] === 'many') {
1994 955
            $mapping['association'] = self::REFERENCE_MANY;
1995
        }
1996 1392
        if (isset($mapping['embedded']) && $mapping['type'] === 'one') {
1997 944
            $mapping['association'] = self::EMBED_ONE;
1998
        }
1999 1392
        if (isset($mapping['embedded']) && $mapping['type'] === 'many') {
2000 978
            $mapping['association'] = self::EMBED_MANY;
2001
        }
2002
2003 1392
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
2004 124
            $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
2005
        }
2006
2007
        /*
2008
        if (isset($mapping['type']) && ($mapping['type'] === 'one' || $mapping['type'] === 'many')) {
2009
            $mapping['type'] = $mapping['type'] === 'one' ? self::ONE : self::MANY;
2010
        }
2011
        */
2012 1392
        if (isset($mapping['version'])) {
2013 67
            $mapping['notSaved'] = true;
2014 67
            $this->setVersionMapping($mapping);
2015
        }
2016 1392
        if (isset($mapping['lock'])) {
2017 22
            $mapping['notSaved'] = true;
2018 22
            $this->setLockMapping($mapping);
2019
        }
2020 1392
        $mapping['isOwningSide'] = true;
2021 1392
        $mapping['isInverseSide'] = false;
2022 1392
        if (isset($mapping['reference'])) {
2023 1065
            if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
2024 84
                $mapping['isOwningSide'] = true;
2025 84
                $mapping['isInverseSide'] = false;
2026
            }
2027 1065
            if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
2028 814
                $mapping['isInverseSide'] = true;
2029 814
                $mapping['isOwningSide'] = false;
2030
            }
2031 1065
            if (isset($mapping['repositoryMethod'])) {
2032 58
                $mapping['isInverseSide'] = true;
2033 58
                $mapping['isOwningSide'] = false;
2034
            }
2035 1065
            if (! isset($mapping['orphanRemoval'])) {
2036 1045
                $mapping['orphanRemoval'] = false;
2037
            }
2038
        }
2039
2040 1392
        if (! empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || ! $mapping['isInverseSide'])) {
2041
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
2042
        }
2043
2044 1392
        $this->applyStorageStrategy($mapping);
2045
2046 1391
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
2047 1391
        if (isset($mapping['association'])) {
2048 1199
            $this->associationMappings[$mapping['fieldName']] = $mapping;
2049
        }
2050
2051 1391
        $reflProp = $this->reflClass->getProperty($mapping['fieldName']);
2052 1390
        $reflProp->setAccessible(true);
2053 1390
        $this->reflFields[$mapping['fieldName']] = $reflProp;
2054
2055 1390
        return $mapping;
2056
    }
2057
2058
    /**
2059
     * Determines which fields get serialized.
2060
     *
2061
     * It is only serialized what is necessary for best unserialization performance.
2062
     * That means any metadata properties that are not set or empty or simply have
2063
     * their default value are NOT serialized.
2064
     *
2065
     * Parts that are also NOT serialized because they can not be properly unserialized:
2066
     *      - reflClass (ReflectionClass)
2067
     *      - reflFields (ReflectionProperty array)
2068
     *
2069
     * @return array The names of all the fields that should be serialized.
2070
     */
2071 6
    public function __sleep()
2072
    {
2073
        // This metadata is always serialized/cached.
2074
        $serialized = [
2075 6
            'fieldMappings',
2076
            'associationMappings',
2077
            'identifier',
2078
            'name',
2079
            'db',
2080
            'collection',
2081
            'readPreference',
2082
            'readPreferenceTags',
2083
            'writeConcern',
2084
            'rootDocumentName',
2085
            'generatorType',
2086
            'generatorOptions',
2087
            'idGenerator',
2088
            'indexes',
2089
            'shardKey',
2090
        ];
2091
2092
        // The rest of the metadata is only serialized if necessary.
2093 6
        if ($this->changeTrackingPolicy !== self::CHANGETRACKING_DEFERRED_IMPLICIT) {
2094
            $serialized[] = 'changeTrackingPolicy';
2095
        }
2096
2097 6
        if ($this->customRepositoryClassName) {
2098 1
            $serialized[] = 'customRepositoryClassName';
2099
        }
2100
2101 6
        if ($this->inheritanceType !== self::INHERITANCE_TYPE_NONE || $this->discriminatorField !== null) {
2102 4
            $serialized[] = 'inheritanceType';
2103 4
            $serialized[] = 'discriminatorField';
2104 4
            $serialized[] = 'discriminatorValue';
2105 4
            $serialized[] = 'discriminatorMap';
2106 4
            $serialized[] = 'defaultDiscriminatorValue';
2107 4
            $serialized[] = 'parentClasses';
2108 4
            $serialized[] = 'subClasses';
2109
        }
2110
2111 6
        if ($this->isMappedSuperclass) {
2112 1
            $serialized[] = 'isMappedSuperclass';
2113
        }
2114
2115 6
        if ($this->isEmbeddedDocument) {
2116 1
            $serialized[] = 'isEmbeddedDocument';
2117
        }
2118
2119 6
        if ($this->isQueryResultDocument) {
2120
            $serialized[] = 'isQueryResultDocument';
2121
        }
2122
2123 6
        if ($this->isVersioned) {
2124
            $serialized[] = 'isVersioned';
2125
            $serialized[] = 'versionField';
2126
        }
2127
2128 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...
2129
            $serialized[] = 'lifecycleCallbacks';
2130
        }
2131
2132 6
        if ($this->collectionCapped) {
2133 1
            $serialized[] = 'collectionCapped';
2134 1
            $serialized[] = 'collectionSize';
2135 1
            $serialized[] = 'collectionMax';
2136
        }
2137
2138 6
        if ($this->isReadOnly) {
2139
            $serialized[] = 'isReadOnly';
2140
        }
2141
2142 6
        return $serialized;
2143
    }
2144
2145
    /**
2146
     * Restores some state that can not be serialized/unserialized.
2147
     *
2148
     */
2149 6
    public function __wakeup()
2150
    {
2151
        // Restore ReflectionClass and properties
2152 6
        $this->reflClass = new \ReflectionClass($this->name);
2153 6
        $this->instantiator = $this->instantiator ?: new Instantiator();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->instantiator ?: n...antiator\Instantiator() can also be of type object<Doctrine\Instantiator\Instantiator>. However, the property $instantiator is declared as type object<Doctrine\Instanti...antiatorInterface>|null. Maybe add an additional type check?

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

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

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

class Id
{
    public $id;

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

}

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

$account_id = false;

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

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2154
2155 6
        foreach ($this->fieldMappings as $field => $mapping) {
2156 3
            if (isset($mapping['declared'])) {
2157 1
                $reflField = new \ReflectionProperty($mapping['declared'], $field);
2158
            } else {
2159 3
                $reflField = $this->reflClass->getProperty($field);
2160
            }
2161 3
            $reflField->setAccessible(true);
2162 3
            $this->reflFields[$field] = $reflField;
2163
        }
2164 6
    }
2165
2166
    /**
2167
     * Creates a new instance of the mapped class, without invoking the constructor.
2168
     *
2169
     * @return object
2170
     */
2171 355
    public function newInstance()
2172
    {
2173 355
        return $this->instantiator->instantiate($this->name);
2174
    }
2175
}
2176