Completed
Pull Request — master (#1757)
by Maciej
15:59
created

ClassMetadata   F

Complexity

Total Complexity 293

Size/Duplication

Total Lines 2154
Duplicated Lines 0 %

Coupling/Cohesion

Components 9
Dependencies 4

Test Coverage

Coverage 85.89%

Importance

Changes 0
Metric Value
wmc 293
lcom 9
cbo 4
dl 0
loc 2154
ccs 499
cts 581
cp 0.8589
rs 0.6314
c 0
b 0
f 0

112 Methods

Rating   Name   Duplication   Size   Complexity  
A getIndexes() 0 4 1
A hasIndexes() 0 4 2
A __construct() 0 9 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
B setCustomRepositoryClass() 0 12 6
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 19 7
A setDefaultDiscriminatorValue() 0 14 3
A setDiscriminatorValue() 0 5 1
B addIndex() 0 22 6
C setShardKey() 0 44 13
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 getNamespace() 0 4 1
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 10 4
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 174 73
F __sleep() 0 74 12
A __wakeup() 0 16 4
A newInstance() 0 4 1

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