Completed
Push — master ( cc122d...e5911d )
by Maciej
14s
created

ClassMetadataInfo   F

Complexity

Total Complexity 305

Size/Duplication

Total Lines 2143
Duplicated Lines 3.45 %

Coupling/Cohesion

Components 10
Dependencies 5

Test Coverage

Coverage 84.83%

Importance

Changes 0
Metric Value
wmc 305
lcom 10
cbo 5
dl 74
loc 2143
ccs 481
cts 567
cp 0.8483
rs 0.5217
c 0
b 0
f 0

116 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 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() 3 12 6
B invokeLifecycleCallbacks() 0 18 5
A hasLifecycleCallbacks() 0 4 1
A getLifecycleCallbacks() 0 4 2
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
A setSlaveOkay() 0 11 3
B addIndex() 14 20 6
A setRequireIndexes() 0 11 2
A getIndexes() 0 4 1
A hasIndexes() 0 4 2
C setShardKey() 14 42 13
A getShardKey() 0 4 1
A isSharded() 0 4 2
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
B setCollection() 0 14 6
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
A isFile() 0 4 2
A getFile() 0 4 1
A setFile() 0 4 1
A getDistance() 0 4 1
A setDistance() 0 4 1
F mapField() 43 194 86
C applyStorageStrategy() 0 44 14
A mapFile() 0 6 1
A mapOneEmbedded() 0 6 1
A mapManyEmbedded() 0 6 1
A mapOneReference() 0 6 1
A mapManyReference() 0 6 1
A addInheritedFieldMapping() 0 8 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 7 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 8 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

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like ClassMetadataInfo 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 ClassMetadataInfo, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ODM\MongoDB\Mapping;
21
22
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
23
use Doctrine\ODM\MongoDB\LockException;
24
use Doctrine\ODM\MongoDB\Proxy\Proxy;
25
use Doctrine\ODM\MongoDB\Types\Type;
26
use InvalidArgumentException;
27
28
/**
29
 * A <tt>ClassMetadata</tt> instance holds all the object-document mapping metadata
30
 * of a document and it's references.
31
 *
32
 * Once populated, ClassMetadata instances are usually cached in a serialized form.
33
 *
34
 * <b>IMPORTANT NOTE:</b>
35
 *
36
 * The fields of this class are only public for 2 reasons:
37
 * 1) To allow fast READ access.
38
 * 2) To drastically reduce the size of a serialized instance (private/protected members
39
 *    get the whole class name, namespace inclusive, prepended to every property in
40
 *    the serialized representation).
41
 *
42
 * @since       1.0
43
 */
44
class ClassMetadataInfo implements \Doctrine\Common\Persistence\Mapping\ClassMetadata
45
{
46
    /* The Id generator types. */
47
    /**
48
     * AUTO means Doctrine will automatically create a new \MongoId instance for us.
49
     */
50
    const GENERATOR_TYPE_AUTO = 1;
51
52
    /**
53
     * INCREMENT means a separate collection is used for maintaining and incrementing id generation.
54
     * Offers full portability.
55
     */
56
    const GENERATOR_TYPE_INCREMENT = 2;
57
58
    /**
59
     * UUID means Doctrine will generate a uuid for us.
60
     */
61
    const GENERATOR_TYPE_UUID = 3;
62
63
    /**
64
     * ALNUM means Doctrine will generate Alpha-numeric string identifiers, using the INCREMENT
65
     * generator to ensure identifier uniqueness
66
     */
67
    const GENERATOR_TYPE_ALNUM = 4;
68
69
    /**
70
     * CUSTOM means Doctrine expect a class parameter. It will then try to initiate that class
71
     * and pass other options to the generator. It will throw an Exception if the class
72
     * does not exist or if an option was passed for that there is not setter in the new
73
     * generator class.
74
     *
75
     * The class  will have to be a subtype of AbstractIdGenerator.
76
     */
77
    const GENERATOR_TYPE_CUSTOM = 5;
78
79
    /**
80
     * NONE means Doctrine will not generate any id for us and you are responsible for manually
81
     * assigning an id.
82
     */
83
    const GENERATOR_TYPE_NONE = 6;
84
85
    /**
86
     * Default discriminator field name.
87
     *
88
     * This is used for associations value for associations where a that do not define a "targetDocument" or
89
     * "discriminatorField" option in their mapping.
90
     */
91
    const DEFAULT_DISCRIMINATOR_FIELD = '_doctrine_class_name';
92
93
    const REFERENCE_ONE = 1;
94
    const REFERENCE_MANY = 2;
95
    const EMBED_ONE = 3;
96
    const EMBED_MANY = 4;
97
    const MANY = 'many';
98
    const ONE = 'one';
99
100
    /**
101
     * The types of storeAs references
102
     */
103
    const REFERENCE_STORE_AS_ID = 'id';
104
    const REFERENCE_STORE_AS_DB_REF = 'dbRef';
105
    const REFERENCE_STORE_AS_DB_REF_WITH_DB = 'dbRefWithDb';
106
    const REFERENCE_STORE_AS_REF = 'ref';
107
108
    /* The inheritance mapping types */
109
    /**
110
     * NONE means the class does not participate in an inheritance hierarchy
111
     * and therefore does not need an inheritance mapping type.
112
     */
113
    const INHERITANCE_TYPE_NONE = 1;
114
115
    /**
116
     * SINGLE_COLLECTION means the class will be persisted according to the rules of
117
     * <tt>Single Collection Inheritance</tt>.
118
     */
119
    const INHERITANCE_TYPE_SINGLE_COLLECTION = 2;
120
121
    /**
122
     * COLLECTION_PER_CLASS means the class will be persisted according to the rules
123
     * of <tt>Concrete Collection Inheritance</tt>.
124
     */
125
    const INHERITANCE_TYPE_COLLECTION_PER_CLASS = 3;
126
127
    /**
128
     * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time
129
     * by doing a property-by-property comparison with the original data. This will
130
     * be done for all entities that are in MANAGED state at commit-time.
131
     *
132
     * This is the default change tracking policy.
133
     */
134
    const CHANGETRACKING_DEFERRED_IMPLICIT = 1;
135
136
    /**
137
     * DEFERRED_EXPLICIT 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 only for entities that were explicitly saved (through persist() or a cascade).
140
     */
141
    const CHANGETRACKING_DEFERRED_EXPLICIT = 2;
142
143
    /**
144
     * NOTIFY means that Doctrine relies on the entities sending out notifications
145
     * when their properties change. Such entity classes must implement
146
     * the <tt>NotifyPropertyChanged</tt> interface.
147
     */
148
    const CHANGETRACKING_NOTIFY = 3;
149
150
    /**
151
     * SET means that fields will be written to the database using a $set operator
152
     */
153
    const STORAGE_STRATEGY_SET = 'set';
154
155
    /**
156
     * INCREMENT means that fields will be written to the database by calculating
157
     * the difference and using the $inc operator
158
     */
159
    const STORAGE_STRATEGY_INCREMENT = 'increment';
160
161
    const STORAGE_STRATEGY_PUSH_ALL = 'pushAll';
162
    const STORAGE_STRATEGY_ADD_TO_SET = 'addToSet';
163
    const STORAGE_STRATEGY_ATOMIC_SET = 'atomicSet';
164
    const STORAGE_STRATEGY_ATOMIC_SET_ARRAY = 'atomicSetArray';
165
    const STORAGE_STRATEGY_SET_ARRAY = 'setArray';
166
167
    /**
168
     * READ-ONLY: The name of the mongo database the document is mapped to.
169
     */
170
    public $db;
171
172
    /**
173
     * READ-ONLY: The name of the mongo collection the document is mapped to.
174
     */
175
    public $collection;
176
177
    /**
178
     * READ-ONLY: If the collection should be a fixed size.
179
     */
180
    public $collectionCapped;
181
182
    /**
183
     * READ-ONLY: If the collection is fixed size, its size in bytes.
184
     */
185
    public $collectionSize;
186
187
    /**
188
     * READ-ONLY: If the collection is fixed size, the maximum number of elements to store in the collection.
189
     */
190
    public $collectionMax;
191
192
    /**
193
     * READ-ONLY: Describes the level of acknowledgement requested from MongoDB for write operations.
194
     */
195
    public $writeConcern;
196
197
    /**
198
     * READ-ONLY: The field name of the document identifier.
199
     */
200
    public $identifier;
201
202
    /**
203
     * READ-ONLY: The field that stores a file reference and indicates the
204
     * document is a file and should be stored on the MongoGridFS.
205
     */
206
    public $file;
207
208
    /**
209
     * READ-ONLY: The field that stores the calculated distance when performing geo spatial
210
     * queries.
211
     */
212
    public $distance;
213
214
    /**
215
     * READ-ONLY: Whether or not reads for this class are okay to read from a slave.
216
     *
217
     * @deprecated in version 1.2 and will be removed in 2.0.
218
     */
219
    public $slaveOkay;
220
221
    /**
222
     * READ-ONLY: The array of indexes for the document collection.
223
     */
224
    public $indexes = array();
225
226
    /**
227
     * READ-ONLY: Keys and options describing shard key. Only for sharded collections.
228
     */
229
    public $shardKey;
230
231
    /**
232
     * READ-ONLY: Whether or not queries on this document should require indexes.
233
     *
234
     * @deprecated property was deprecated in 1.2 and will be removed in 2.0
235
     */
236
    public $requireIndexes = false;
237
238
    /**
239
     * READ-ONLY: The name of the document class.
240
     */
241
    public $name;
242
243
    /**
244
     * READ-ONLY: The namespace the document class is contained in.
245
     *
246
     * @var string
247
     * @todo Not really needed. Usage could be localized.
248
     */
249
    public $namespace;
250
251
    /**
252
     * READ-ONLY: The name of the document class that is at the root of the mapped document inheritance
253
     * hierarchy. If the document is not part of a mapped inheritance hierarchy this is the same
254
     * as {@link $documentName}.
255
     *
256
     * @var string
257
     */
258
    public $rootDocumentName;
259
260
    /**
261
     * The name of the custom repository class used for the document class.
262
     * (Optional).
263
     *
264
     * @var string
265
     */
266
    public $customRepositoryClassName;
267
268
    /**
269
     * READ-ONLY: The names of the parent classes (ancestors).
270
     *
271
     * @var array
272
     */
273
    public $parentClasses = array();
274
275
    /**
276
     * READ-ONLY: The names of all subclasses (descendants).
277
     *
278
     * @var array
279
     */
280
    public $subClasses = array();
281
282
    /**
283
     * The ReflectionProperty instances of the mapped class.
284
     *
285
     * @var \ReflectionProperty[]
286
     */
287
    public $reflFields = array();
288
289
    /**
290
     * READ-ONLY: The inheritance mapping type used by the class.
291
     *
292
     * @var integer
293
     */
294
    public $inheritanceType = self::INHERITANCE_TYPE_NONE;
295
296
    /**
297
     * READ-ONLY: The Id generator type used by the class.
298
     *
299
     * @var string
300
     */
301
    public $generatorType = self::GENERATOR_TYPE_AUTO;
302
303
    /**
304
     * READ-ONLY: The Id generator options.
305
     *
306
     * @var array
307
     */
308
    public $generatorOptions = array();
309
310
    /**
311
     * READ-ONLY: The ID generator used for generating IDs for this class.
312
     *
313
     * @var \Doctrine\ODM\MongoDB\Id\AbstractIdGenerator
314
     */
315
    public $idGenerator;
316
317
    /**
318
     * READ-ONLY: The field mappings of the class.
319
     * Keys are field names and values are mapping definitions.
320
     *
321
     * The mapping definition array has the following values:
322
     *
323
     * - <b>fieldName</b> (string)
324
     * The name of the field in the Document.
325
     *
326
     * - <b>id</b> (boolean, optional)
327
     * Marks the field as the primary key of the document. Multiple fields of an
328
     * document can have the id attribute, forming a composite key.
329
     *
330
     * @var array
331
     */
332
    public $fieldMappings = array();
333
334
    /**
335
     * READ-ONLY: The association mappings of the class.
336
     * Keys are field names and values are mapping definitions.
337
     *
338
     * @var array
339
     */
340
    public $associationMappings = array();
341
342
    /**
343
     * READ-ONLY: Array of fields to also load with a given method.
344
     *
345
     * @var array
346
     */
347
    public $alsoLoadMethods = array();
348
349
    /**
350
     * READ-ONLY: The registered lifecycle callbacks for documents of this class.
351
     *
352
     * @var array
353
     */
354
    public $lifecycleCallbacks = array();
355
356
    /**
357
     * READ-ONLY: The discriminator value of this class.
358
     *
359
     * <b>This does only apply to the JOINED and SINGLE_COLLECTION inheritance mapping strategies
360
     * where a discriminator field is used.</b>
361
     *
362
     * @var mixed
363
     * @see discriminatorField
364
     */
365
    public $discriminatorValue;
366
367
    /**
368
     * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
369
     *
370
     * <b>This does only apply to the SINGLE_COLLECTION inheritance mapping strategy
371
     * where a discriminator field is used.</b>
372
     *
373
     * @var mixed
374
     * @see discriminatorField
375
     */
376
    public $discriminatorMap = array();
377
378
    /**
379
     * READ-ONLY: The definition of the discriminator field used in SINGLE_COLLECTION
380
     * inheritance mapping.
381
     *
382
     * @var string
383
     */
384
    public $discriminatorField;
385
386
    /**
387
     * READ-ONLY: The default value for discriminatorField in case it's not set in the document
388
     *
389
     * @var string
390
     * @see discriminatorField
391
     */
392
    public $defaultDiscriminatorValue;
393
394
    /**
395
     * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
396
     *
397
     * @var boolean
398
     */
399
    public $isMappedSuperclass = false;
400
401
    /**
402
     * READ-ONLY: Whether this class describes the mapping of a embedded document.
403
     *
404
     * @var boolean
405
     */
406
    public $isEmbeddedDocument = false;
407
408
    /**
409
     * READ-ONLY: Whether this class describes the mapping of an aggregation result document.
410
     *
411
     * @var boolean
412
     */
413
    public $isQueryResultDocument = false;
414
415
    /**
416
     * READ-ONLY: The policy used for change-tracking on entities of this class.
417
     *
418
     * @var integer
419
     */
420
    public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;
421
422
    /**
423
     * READ-ONLY: A flag for whether or not instances of this class are to be versioned
424
     * with optimistic locking.
425
     *
426
     * @var boolean $isVersioned
427
     */
428
    public $isVersioned;
429
430
    /**
431
     * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
432
     *
433
     * @var mixed $versionField
434
     */
435
    public $versionField;
436
437
    /**
438
     * READ-ONLY: A flag for whether or not instances of this class are to allow pessimistic
439
     * locking.
440
     *
441
     * @var boolean $isLockable
442
     */
443
    public $isLockable;
444
445
    /**
446
     * READ-ONLY: The name of the field which is used for locking a document.
447
     *
448
     * @var mixed $lockField
449
     */
450
    public $lockField;
451
452
    /**
453
     * The ReflectionClass instance of the mapped class.
454
     *
455
     * @var \ReflectionClass
456
     */
457
    public $reflClass;
458
459
    /**
460
     * READ_ONLY: A flag for whether or not this document is read-only.
461
     *
462
     * @var bool
463
     */
464
    public $isReadOnly;
465
466
    /**
467
     * Initializes a new ClassMetadata instance that will hold the object-document mapping
468
     * metadata of the class with the given name.
469
     *
470
     * @param string $documentName The name of the document class the new instance is used for.
471
     */
472 1001
    public function __construct($documentName)
473
    {
474 1001
        $this->name = $documentName;
475 1001
        $this->rootDocumentName = $documentName;
476 1001
    }
477
478
    /**
479
     * Helper method to get reference id of ref* type references
480
     * @param mixed  $reference
481
     * @param string $storeAs
482
     * @return mixed
483
     * @internal
484
     */
485 117
    public static function getReferenceId($reference, $storeAs)
486
    {
487 117
        return $storeAs === ClassMetadataInfo::REFERENCE_STORE_AS_ID ? $reference : $reference[ClassMetadataInfo::getReferencePrefix($storeAs) . 'id'];
488
    }
489
490
    /**
491
     * Returns the reference prefix used for a reference
492
     * @param string $storeAs
493
     * @return string
494
     */
495 196
    private static function getReferencePrefix($storeAs)
496
    {
497 196
        if (!in_array($storeAs, [ClassMetadataInfo::REFERENCE_STORE_AS_REF, ClassMetadataInfo::REFERENCE_STORE_AS_DB_REF, ClassMetadataInfo::REFERENCE_STORE_AS_DB_REF_WITH_DB])) {
498
            throw new \LogicException('Can only get a reference prefix for DBRef and reference arrays');
499
        }
500
501 196
        return $storeAs === ClassMetadataInfo::REFERENCE_STORE_AS_REF ? '' : '$';
502
    }
503
504
    /**
505
     * Returns a fully qualified field name for a given reference
506
     * @param string $storeAs
507
     * @param string $pathPrefix The field path prefix
508
     * @return string
509
     * @internal
510
     */
511 142
    public static function getReferenceFieldName($storeAs, $pathPrefix = '')
512
    {
513 142
        if ($storeAs === ClassMetadataInfo::REFERENCE_STORE_AS_ID) {
514 103
            return $pathPrefix;
515
        }
516
517 133
        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...
518
    }
519
520
    /**
521
     * {@inheritDoc}
522
     */
523 929
    public function getReflectionClass()
524
    {
525 929
        if ( ! $this->reflClass) {
526 2
            $this->reflClass = new \ReflectionClass($this->name);
527
        }
528
529 929
        return $this->reflClass;
530
    }
531
532
    /**
533
     * {@inheritDoc}
534
     */
535 337
    public function isIdentifier($fieldName)
536
    {
537 337
        return $this->identifier === $fieldName;
538
    }
539
540
    /**
541
     * INTERNAL:
542
     * Sets the mapped identifier field of this class.
543
     *
544
     * @param string $identifier
545
     */
546 374
    public function setIdentifier($identifier)
547
    {
548 374
        $this->identifier = $identifier;
549 374
    }
550
551
    /**
552
     * {@inheritDoc}
553
     *
554
     * Since MongoDB only allows exactly one identifier field
555
     * this will always return an array with only one value
556
     */
557 40
    public function getIdentifier()
558
    {
559 40
        return array($this->identifier);
560
    }
561
562
    /**
563
     * {@inheritDoc}
564
     *
565
     * Since MongoDB only allows exactly one identifier field
566
     * this will always return an array with only one value
567
     */
568 98
    public function getIdentifierFieldNames()
569
    {
570 98
        return array($this->identifier);
571
    }
572
573
    /**
574
     * {@inheritDoc}
575
     */
576 573
    public function hasField($fieldName)
577
    {
578 573
        return isset($this->fieldMappings[$fieldName]);
579
    }
580
581
    /**
582
     * Sets the inheritance type used by the class and it's subclasses.
583
     *
584
     * @param integer $type
585
     */
586 390
    public function setInheritanceType($type)
587
    {
588 390
        $this->inheritanceType = $type;
589 390
    }
590
591
    /**
592
     * Checks whether a mapped field is inherited from an entity superclass.
593
     *
594
     * @param  string $fieldName
595
     *
596
     * @return boolean TRUE if the field is inherited, FALSE otherwise.
597
     */
598 929
    public function isInheritedField($fieldName)
599
    {
600 929
        return isset($this->fieldMappings[$fieldName]['inherited']);
601
    }
602
603
    /**
604
     * Registers a custom repository class for the document class.
605
     *
606
     * @param string $repositoryClassName The class name of the custom repository.
607
     */
608 322
    public function setCustomRepositoryClass($repositoryClassName)
609
    {
610 322
        if ($this->isEmbeddedDocument || $this->isQueryResultDocument) {
611
            return;
612
        }
613
614 322 View Code Duplication
        if ($repositoryClassName && strpos($repositoryClassName, '\\') === false && strlen($this->namespace)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
615 4
            $repositoryClassName = $this->namespace . '\\' . $repositoryClassName;
616
        }
617
618 322
        $this->customRepositoryClassName = $repositoryClassName;
619 322
    }
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 678
    public function invokeLifecycleCallbacks($event, $document, array $arguments = null)
632
    {
633 678
        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 677
        if (empty($this->lifecycleCallbacks[$event])) {
638 663
            return;
639
        }
640
641 200
        foreach ($this->lifecycleCallbacks[$event] as $callback) {
642 200
            if ($arguments !== null) {
643 199
                call_user_func_array(array($document, $callback), $arguments);
644
            } else {
645 200
                $document->$callback();
646
            }
647
        }
648 200
    }
649
650
    /**
651
     * Checks whether the class has callbacks registered for a lifecycle event.
652
     *
653
     * @param string $event Lifecycle event
654
     *
655
     * @return boolean
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 isset($this->lifecycleCallbacks[$event]) ? $this->lifecycleCallbacks[$event] : array();
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 301
    public function addLifecycleCallback($callback, $event)
682
    {
683 301
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
684 1
            return;
685
        }
686
687 301
        $this->lifecycleCallbacks[$event][] = $callback;
688 301
    }
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 373
    public function setLifecycleCallbacks(array $callbacks)
698
    {
699 373
        $this->lifecycleCallbacks = $callbacks;
700 373
    }
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 15
    public function registerAlsoLoadMethod($method, $fields)
712
    {
713 15
        $this->alsoLoadMethods[$method] = is_array($fields) ? $fields : array($fields);
714 15
    }
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 373
    public function setAlsoLoadMethods(array $methods)
724
    {
725 373
        $this->alsoLoadMethods = $methods;
726 373
    }
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 403
    public function setDiscriminatorField($discriminatorField)
741
    {
742 403
        if ($discriminatorField === null) {
743 330
            $this->discriminatorField = null;
744
745 330
            return;
746
        }
747
748
        // Handle array argument with name/fieldName keys for BC
749 130
        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 130
        foreach ($this->fieldMappings as $fieldMapping) {
758 4
            if ($discriminatorField == $fieldMapping['name']) {
759 4
                throw MappingException::discriminatorFieldConflict($this->name, $discriminatorField);
760
            }
761
        }
762
763 129
        $this->discriminatorField = $discriminatorField;
764 129
    }
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 396
    public function setDiscriminatorMap(array $map)
775
    {
776 396
        foreach ($map as $value => $className) {
777 125
            if (strpos($className, '\\') === false && strlen($this->namespace)) {
778 91
                $className = $this->namespace . '\\' . $className;
779
            }
780 125
            $this->discriminatorMap[$value] = $className;
781 125
            if ($this->name == $className) {
782 117
                $this->discriminatorValue = $value;
783
            } else {
784 120
                if ( ! class_exists($className)) {
785
                    throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
786
                }
787 120
                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...
788 125
                    $this->subClasses[] = $className;
789
                }
790
            }
791
        }
792 396
    }
793
794
    /**
795
     * Sets the default discriminator value to be used for this class
796
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies if the document has no discriminator value
797
     *
798
     * @param string $defaultDiscriminatorValue
799
     *
800
     * @throws MappingException
801
     */
802 380
    public function setDefaultDiscriminatorValue($defaultDiscriminatorValue)
803
    {
804 380
        if ($defaultDiscriminatorValue === null) {
805 373
            $this->defaultDiscriminatorValue = null;
806
807 373
            return;
808
        }
809
810 60
        if (!array_key_exists($defaultDiscriminatorValue, $this->discriminatorMap)) {
811
            throw MappingException::invalidDiscriminatorValue($defaultDiscriminatorValue, $this->name);
812
        }
813
814 60
        $this->defaultDiscriminatorValue = $defaultDiscriminatorValue;
815 60
    }
816
817
    /**
818
     * Sets the discriminator value for this class.
819
     * Used for JOINED/SINGLE_TABLE inheritance and multiple document types in a single
820
     * collection.
821
     *
822
     * @param string $value
823
     */
824 3
    public function setDiscriminatorValue($value)
825
    {
826 3
        $this->discriminatorMap[$value] = $this->name;
827 3
        $this->discriminatorValue = $value;
828 3
    }
829
830
    /**
831
     * Sets the slaveOkay option applied to collections for this class.
832
     *
833
     * @param boolean|null $slaveOkay
834
     *
835
     * @deprecated in version 1.2 and will be removed in 2.0.
836
     */
837 3
    public function setSlaveOkay($slaveOkay)
838
    {
839 3
        if ($slaveOkay) {
840 2
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
841 2
                sprintf('%s was deprecated in version 1.2 and will be removed in 2.0.', __METHOD__),
842 2
                E_USER_DEPRECATED
843
            );
844
        }
845
846 3
        $this->slaveOkay = $slaveOkay === null ? null : (boolean) $slaveOkay;
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ODM\MongoDB\Map...etadataInfo::$slaveOkay has been deprecated with message: in version 1.2 and will be removed in 2.0.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
847 3
    }
848
849
    /**
850
     * Add a index for this Document.
851
     *
852
     * @param array $keys Array of keys for the index.
853
     * @param array $options Array of options for the index.
854
     */
855 235
    public function addIndex($keys, array $options = array())
856
    {
857 235
        $this->indexes[] = array(
858 235 View Code Duplication
            'keys' => array_map(function($value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
859 235
                if ($value == 1 || $value == -1) {
860 63
                    return (int) $value;
861
                }
862 227
                if (is_string($value)) {
863 227
                    $lower = strtolower($value);
864 227
                    if ($lower === 'asc') {
865 220
                        return 1;
866 11
                    } elseif ($lower === 'desc') {
867 4
                        return -1;
868
                    }
869
                }
870 7
                return $value;
871 235
            }, $keys),
872 235
            'options' => $options
873
        );
874 235
    }
875
876
    /**
877
     * Set whether or not queries on this document should require indexes.
878
     *
879
     * @param bool $requireIndexes
880
     *
881
     * @deprecated method was deprecated in 1.2 and will be removed in 2.0
882
     */
883 920
    public function setRequireIndexes($requireIndexes)
884
    {
885 920
        if ($requireIndexes) {
886 24
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
887 24
                'requireIndexes was deprecated in version 1.2 and will be removed altogether in 2.0.',
888 24
                E_USER_DEPRECATED
889
            );
890
        }
891
892 920
        $this->requireIndexes = $requireIndexes;
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ODM\MongoDB\Map...taInfo::$requireIndexes has been deprecated with message: property was deprecated in 1.2 and will be removed in 2.0

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
893 920
    }
894
895
    /**
896
     * Returns the array of indexes for this Document.
897
     *
898
     * @return array $indexes The array of indexes.
899
     */
900 54
    public function getIndexes()
901
    {
902 54
        return $this->indexes;
903
    }
904
905
    /**
906
     * Checks whether this document has indexes or not.
907
     *
908
     * @return boolean
909
     */
910
    public function hasIndexes()
911
    {
912
        return $this->indexes ? true : false;
913
    }
914
915
    /**
916
     * Set shard key for this Document.
917
     *
918
     * @param array $keys Array of document keys.
919
     * @param array $options Array of sharding options.
920
     *
921
     * @throws MappingException
922
     */
923 87
    public function setShardKey(array $keys, array $options = array())
924
    {
925 87
        if ($this->inheritanceType === self::INHERITANCE_TYPE_SINGLE_COLLECTION && !is_null($this->shardKey)) {
926 2
            throw MappingException::shardKeyInSingleCollInheritanceSubclass($this->getName());
927
        }
928
929 87
        if ($this->isEmbeddedDocument) {
930 2
            throw MappingException::embeddedDocumentCantHaveShardKey($this->getName());
931
        }
932
933 85
        foreach (array_keys($keys) as $field) {
934 85
            if (! isset($this->fieldMappings[$field])) {
935 78
                continue;
936
            }
937
938 7
            if (in_array($this->fieldMappings[$field]['type'], ['many', 'collection'])) {
939 3
                throw MappingException::noMultiKeyShardKeys($this->getName(), $field);
940
            }
941
942 4
            if ($this->fieldMappings[$field]['strategy'] !== static::STORAGE_STRATEGY_SET) {
943 4
                throw MappingException::onlySetStrategyAllowedInShardKey($this->getName(), $field);
944
            }
945
        }
946
947 81
        $this->shardKey = array(
948 81 View Code Duplication
            'keys' => array_map(function($value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
949 81
                if ($value == 1 || $value == -1) {
950 6
                    return (int) $value;
951
                }
952 80
                if (is_string($value)) {
953 80
                    $lower = strtolower($value);
954 80
                    if ($lower === 'asc') {
955 79
                        return 1;
956 53
                    } elseif ($lower === 'desc') {
957
                        return -1;
958
                    }
959
                }
960 53
                return $value;
961 81
            }, $keys),
962 81
            'options' => $options
963
        );
964 81
    }
965
966
    /**
967
     * @return array
968
     */
969 28
    public function getShardKey()
970
    {
971 28
        return $this->shardKey;
972
    }
973
974
    /**
975
     * Checks whether this document has shard key or not.
976
     *
977
     * @return bool
978
     */
979 616
    public function isSharded()
980
    {
981 616
        return $this->shardKey ? true : false;
982
    }
983
984
    /**
985
     * Sets the write concern used by this class.
986
     *
987
     * @param string $writeConcern
988
     */
989 387
    public function setWriteConcern($writeConcern)
990
    {
991 387
        $this->writeConcern = $writeConcern;
992 387
    }
993
994
    /**
995
     * @return string
996
     */
997 12
    public function getWriteConcern()
998
    {
999 12
        return $this->writeConcern;
1000
    }
1001
1002
    /**
1003
     * Whether there is a write concern configured for this class.
1004
     *
1005
     * @return bool
1006
     */
1007 622
    public function hasWriteConcern()
1008
    {
1009 622
        return $this->writeConcern !== null;
1010
    }
1011
1012
    /**
1013
     * Sets the change tracking policy used by this class.
1014
     *
1015
     * @param integer $policy
1016
     */
1017 378
    public function setChangeTrackingPolicy($policy)
1018
    {
1019 378
        $this->changeTrackingPolicy = $policy;
1020 378
    }
1021
1022
    /**
1023
     * Whether the change tracking policy of this class is "deferred explicit".
1024
     *
1025
     * @return boolean
1026
     */
1027 75
    public function isChangeTrackingDeferredExplicit()
1028
    {
1029 75
        return $this->changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_EXPLICIT;
1030
    }
1031
1032
    /**
1033
     * Whether the change tracking policy of this class is "deferred implicit".
1034
     *
1035
     * @return boolean
1036
     */
1037 643
    public function isChangeTrackingDeferredImplicit()
1038
    {
1039 643
        return $this->changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_IMPLICIT;
1040
    }
1041
1042
    /**
1043
     * Whether the change tracking policy of this class is "notify".
1044
     *
1045
     * @return boolean
1046
     */
1047 352
    public function isChangeTrackingNotify()
1048
    {
1049 352
        return $this->changeTrackingPolicy == self::CHANGETRACKING_NOTIFY;
1050
    }
1051
1052
    /**
1053
     * Gets the ReflectionProperties of the mapped class.
1054
     *
1055
     * @return array An array of ReflectionProperty instances.
1056
     */
1057 98
    public function getReflectionProperties()
1058
    {
1059 98
        return $this->reflFields;
1060
    }
1061
1062
    /**
1063
     * Gets a ReflectionProperty for a specific field of the mapped class.
1064
     *
1065
     * @param string $name
1066
     *
1067
     * @return \ReflectionProperty
1068
     */
1069
    public function getReflectionProperty($name)
1070
    {
1071
        return $this->reflFields[$name];
1072
    }
1073
1074
    /**
1075
     * {@inheritDoc}
1076
     */
1077 935
    public function getName()
1078
    {
1079 935
        return $this->name;
1080
    }
1081
1082
    /**
1083
     * The namespace this Document class belongs to.
1084
     *
1085
     * @return string $namespace The namespace name.
1086
     */
1087
    public function getNamespace()
1088
    {
1089
        return $this->namespace;
1090
    }
1091
1092
    /**
1093
     * Returns the database this Document is mapped to.
1094
     *
1095
     * @return string $db The database name.
1096
     */
1097 854
    public function getDatabase()
1098
    {
1099 854
        return $this->db;
1100
    }
1101
1102
    /**
1103
     * Set the database this Document is mapped to.
1104
     *
1105
     * @param string $db The database name
1106
     */
1107 104
    public function setDatabase($db)
1108
    {
1109 104
        $this->db = $db;
1110 104
    }
1111
1112
    /**
1113
     * Get the collection this Document is mapped to.
1114
     *
1115
     * @return string $collection The collection name.
1116
     */
1117 859
    public function getCollection()
1118
    {
1119 859
        return $this->collection;
1120
    }
1121
1122
    /**
1123
     * Sets the collection this Document is mapped to.
1124
     *
1125
     * @param array|string $name
1126
     *
1127
     * @throws \InvalidArgumentException
1128
     */
1129 965
    public function setCollection($name)
1130
    {
1131 965
        if (is_array($name)) {
1132
            if ( ! isset($name['name'])) {
1133
                throw new \InvalidArgumentException('A name key is required when passing an array to setCollection()');
1134
            }
1135
            $this->collectionCapped = isset($name['capped']) ? $name['capped'] : false;
1136
            $this->collectionSize = isset($name['size']) ? $name['size'] : 0;
1137
            $this->collectionMax = isset($name['max']) ? $name['max'] : 0;
1138
            $this->collection = $name['name'];
1139
        } else {
1140 965
            $this->collection = $name;
1141
        }
1142 965
    }
1143
1144
    /**
1145
     * Get whether or not the documents collection is capped.
1146
     *
1147
     * @return boolean
1148
     */
1149 4
    public function getCollectionCapped()
1150
    {
1151 4
        return $this->collectionCapped;
1152
    }
1153
1154
    /**
1155
     * Set whether or not the documents collection is capped.
1156
     *
1157
     * @param boolean $bool
1158
     */
1159 1
    public function setCollectionCapped($bool)
1160
    {
1161 1
        $this->collectionCapped = $bool;
1162 1
    }
1163
1164
    /**
1165
     * Get the collection size
1166
     *
1167
     * @return integer
1168
     */
1169 4
    public function getCollectionSize()
1170
    {
1171 4
        return $this->collectionSize;
1172
    }
1173
1174
    /**
1175
     * Set the collection size.
1176
     *
1177
     * @param integer $size
1178
     */
1179 1
    public function setCollectionSize($size)
1180
    {
1181 1
        $this->collectionSize = $size;
1182 1
    }
1183
1184
    /**
1185
     * Get the collection max.
1186
     *
1187
     * @return integer
1188
     */
1189 4
    public function getCollectionMax()
1190
    {
1191 4
        return $this->collectionMax;
1192
    }
1193
1194
    /**
1195
     * Set the collection max.
1196
     *
1197
     * @param integer $max
1198
     */
1199 1
    public function setCollectionMax($max)
1200
    {
1201 1
        $this->collectionMax = $max;
1202 1
    }
1203
1204
    /**
1205
     * Returns TRUE if this Document is mapped to a collection FALSE otherwise.
1206
     *
1207
     * @return boolean
1208
     */
1209
    public function isMappedToCollection()
1210
    {
1211
        return $this->collection ? true : false;
1212
    }
1213
1214
    /**
1215
     * Returns TRUE if this Document is a file to be stored on the MongoGridFS FALSE otherwise.
1216
     *
1217
     * @return boolean
1218
     */
1219 800
    public function isFile()
1220
    {
1221 800
        return $this->file ? true : false;
1222
    }
1223
1224
    /**
1225
     * Returns the file field name.
1226
     *
1227
     * @return string $file The file field name.
1228
     */
1229 373
    public function getFile()
1230
    {
1231 373
        return $this->file;
1232
    }
1233
1234
    /**
1235
     * Set the field name that stores the grid file.
1236
     *
1237
     * @param string $file
1238
     */
1239 374
    public function setFile($file)
1240
    {
1241 374
        $this->file = $file;
1242 374
    }
1243
1244
    /**
1245
     * Returns the distance field name.
1246
     *
1247
     * @return string $distance The distance field name.
1248
     */
1249
    public function getDistance()
1250
    {
1251
        return $this->distance;
1252
    }
1253
1254
    /**
1255
     * Set the field name that stores the distance.
1256
     *
1257
     * @param string $distance
1258
     */
1259 1
    public function setDistance($distance)
1260
    {
1261 1
        $this->distance = $distance;
1262 1
    }
1263
1264
    /**
1265
     * Map a field.
1266
     *
1267
     * @param array $mapping The mapping information.
1268
     *
1269
     * @return array
1270
     *
1271
     * @throws MappingException
1272
     */
1273 979
    public function mapField(array $mapping)
1274
    {
1275 979
        if ( ! isset($mapping['fieldName']) && isset($mapping['name'])) {
1276 10
            $mapping['fieldName'] = $mapping['name'];
1277
        }
1278 979
        if ( ! isset($mapping['fieldName'])) {
1279
            throw MappingException::missingFieldName($this->name);
1280
        }
1281 979
        if ( ! isset($mapping['name'])) {
1282 969
            $mapping['name'] = $mapping['fieldName'];
1283
        }
1284 979
        if ($this->identifier === $mapping['name'] && empty($mapping['id'])) {
1285 1
            throw MappingException::mustNotChangeIdentifierFieldsType($this->name, $mapping['name']);
1286
        }
1287 978
        if (isset($this->fieldMappings[$mapping['fieldName']])) {
1288
            //throw MappingException::duplicateFieldMapping($this->name, $mapping['fieldName']);
1289
        }
1290 978
        if ($this->discriminatorField !== null && $this->discriminatorField == $mapping['name']) {
1291 1
            throw MappingException::discriminatorFieldConflict($this->name, $this->discriminatorField);
1292
        }
1293 977 View Code Duplication
        if (isset($mapping['targetDocument']) && strpos($mapping['targetDocument'], '\\') === false && strlen($this->namespace)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1294 627
            $mapping['targetDocument'] = $this->namespace . '\\' . $mapping['targetDocument'];
1295
        }
1296 977
        if (isset($mapping['collectionClass'])) {
1297 65 View Code Duplication
            if (strpos($mapping['collectionClass'], '\\') === false && strlen($this->namespace)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1298 63
                $mapping['collectionClass'] = $this->namespace . '\\' . $mapping['collectionClass'];
1299
            }
1300 65
            $mapping['collectionClass'] = ltrim($mapping['collectionClass'], '\\');
1301
        }
1302 977
        if ( ! empty($mapping['collectionClass'])) {
1303 65
            $rColl = new \ReflectionClass($mapping['collectionClass']);
1304 65
            if ( ! $rColl->implementsInterface('Doctrine\\Common\\Collections\\Collection')) {
1305 1
                throw MappingException::collectionClassDoesNotImplementCommonInterface($this->name, $mapping['fieldName'], $mapping['collectionClass']);
1306
            }
1307
        }
1308
1309 976
        if (isset($mapping['discriminatorMap'])) {
1310 129
            foreach ($mapping['discriminatorMap'] as $key => $class) {
1311 129 View Code Duplication
                if (strpos($class, '\\') === false && strlen($this->namespace)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1312 129
                    $mapping['discriminatorMap'][$key] = $this->namespace . '\\' . $class;
1313
                }
1314
            }
1315
        }
1316
1317 976
        if (isset($mapping['cascade']) && isset($mapping['embedded'])) {
1318 1
            throw MappingException::cascadeOnEmbeddedNotAllowed($this->name, $mapping['fieldName']);
1319
        }
1320
1321 975
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', (array) $mapping['cascade']) : array();
1322
1323 975
        if (in_array('all', $cascades) || isset($mapping['embedded'])) {
1324 654
            $cascades = array('remove', 'persist', 'refresh', 'merge', 'detach');
1325
        }
1326
1327 975
        if (isset($mapping['embedded'])) {
1328 611
            unset($mapping['cascade']);
1329 970
        } elseif (isset($mapping['cascade'])) {
1330 413
            $mapping['cascade'] = $cascades;
1331
        }
1332
1333 975
        $mapping['isCascadeRemove'] = in_array('remove', $cascades);
1334 975
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1335 975
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1336 975
        $mapping['isCascadeMerge'] = in_array('merge', $cascades);
1337 975
        $mapping['isCascadeDetach'] = in_array('detach', $cascades);
1338
1339 975
        if (isset($mapping['type']) && $mapping['type'] === 'file') {
1340 63
            $mapping['file'] = true;
1341
        }
1342 975
        if (isset($mapping['type']) && $mapping['type'] === 'increment') {
1343 1
            $mapping['strategy'] = self::STORAGE_STRATEGY_INCREMENT;
1344
        }
1345 975 View Code Duplication
        if (isset($mapping['file']) && $mapping['file'] === true) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1346 63
            $this->file = $mapping['fieldName'];
1347 63
            $mapping['name'] = 'file';
1348
        }
1349 975 View Code Duplication
        if (isset($mapping['distance']) && $mapping['distance'] === true) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1350 7
            $this->distance = $mapping['fieldName'];
1351
        }
1352 975
        if (isset($mapping['id']) && $mapping['id'] === true) {
1353 947
            $mapping['name'] = '_id';
1354 947
            $this->identifier = $mapping['fieldName'];
1355 947 View Code Duplication
            if (isset($mapping['strategy'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1356 928
                $this->generatorType = constant(ClassMetadata::class . '::GENERATOR_TYPE_' . strtoupper($mapping['strategy']));
1357
            }
1358 947
            $this->generatorOptions = isset($mapping['options']) ? $mapping['options'] : array();
1359 947
            switch ($this->generatorType) {
1360 947
                case self::GENERATOR_TYPE_AUTO:
1361 873
                    $mapping['type'] = 'id';
1362 873
                    break;
1363
                default:
1364 157
                    if ( ! empty($this->generatorOptions['type'])) {
1365 56
                        $mapping['type'] = $this->generatorOptions['type'];
1366 101
                    } elseif (empty($mapping['type'])) {
1367 86
                        $mapping['type'] = $this->generatorType === self::GENERATOR_TYPE_INCREMENT ? 'int_id' : 'custom_id';
1368
                    }
1369
            }
1370 947
            unset($this->generatorOptions['type']);
1371
        }
1372
1373 975
        if ( ! isset($mapping['nullable'])) {
1374 53
            $mapping['nullable'] = false;
1375
        }
1376
1377
        // Synchronize the "simple" and "storeAs" mapping information for backwards compatibility
1378 975
        if (isset($mapping['simple']) && ($mapping['simple'] === true || $mapping['simple'] === 'true')) {
1379 297
            $mapping['storeAs'] = ClassMetadataInfo::REFERENCE_STORE_AS_ID;
1380 297
            @trigger_error('"simple" attribute of a reference is deprecated - use storeAs="id" instead.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1381
        }
1382
        // Provide the correct value for the "simple" field for backwards compatibility
1383 975
        if (isset($mapping['storeAs'])) {
1384 594
            $mapping['simple'] = $mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID;
1385
        }
1386
1387 975
        if (isset($mapping['reference'])
1388 975
            && isset($mapping['storeAs'])
1389 975
            && $mapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID
1390 975
            && ! isset($mapping['targetDocument'])
1391
        ) {
1392 3
            throw MappingException::simpleReferenceRequiresTargetDocument($this->name, $mapping['fieldName']);
1393
        }
1394
1395 972
        if (isset($mapping['reference']) && empty($mapping['targetDocument']) && empty($mapping['discriminatorMap']) &&
1396 972
                (isset($mapping['mappedBy']) || isset($mapping['inversedBy']))) {
1397 4
            throw MappingException::owningAndInverseReferencesRequireTargetDocument($this->name, $mapping['fieldName']);
1398
        }
1399
1400 968
        if ($this->isEmbeddedDocument && $mapping['type'] === 'many' && CollectionHelper::isAtomic($mapping['strategy'])) {
1401 1
            throw MappingException::atomicCollectionStrategyNotAllowed($mapping['strategy'], $this->name, $mapping['fieldName']);
1402
        }
1403
1404 967 View Code Duplication
        if (isset($mapping['reference']) && $mapping['type'] === 'one') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1405 527
            $mapping['association'] = self::REFERENCE_ONE;
1406
        }
1407 967 View Code Duplication
        if (isset($mapping['reference']) && $mapping['type'] === 'many') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1408 464
            $mapping['association'] = self::REFERENCE_MANY;
1409
        }
1410 967 View Code Duplication
        if (isset($mapping['embedded']) && $mapping['type'] === 'one') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1411 472
            $mapping['association'] = self::EMBED_ONE;
1412
        }
1413 967 View Code Duplication
        if (isset($mapping['embedded']) && $mapping['type'] === 'many') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1414 509
            $mapping['association'] = self::EMBED_MANY;
1415
        }
1416
1417 967
        if (isset($mapping['association']) && ! isset($mapping['targetDocument']) && ! isset($mapping['discriminatorField'])) {
1418 134
            $mapping['discriminatorField'] = self::DEFAULT_DISCRIMINATOR_FIELD;
1419
        }
1420
1421
        /*
1422
        if (isset($mapping['type']) && ($mapping['type'] === 'one' || $mapping['type'] === 'many')) {
1423
            $mapping['type'] = $mapping['type'] === 'one' ? self::ONE : self::MANY;
1424
        }
1425
        */
1426 967
        if (isset($mapping['version'])) {
1427 102
            $mapping['notSaved'] = true;
1428 102
            $this->setVersionMapping($mapping);
1429
        }
1430 967
        if (isset($mapping['lock'])) {
1431 27
            $mapping['notSaved'] = true;
1432 27
            $this->setLockMapping($mapping);
1433
        }
1434 967
        $mapping['isOwningSide'] = true;
1435 967
        $mapping['isInverseSide'] = false;
1436 967
        if (isset($mapping['reference'])) {
1437 599 View Code Duplication
            if (isset($mapping['inversedBy']) && $mapping['inversedBy']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1438 92
                $mapping['isOwningSide'] = true;
1439 92
                $mapping['isInverseSide'] = false;
1440
            }
1441 599 View Code Duplication
            if (isset($mapping['mappedBy']) && $mapping['mappedBy']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1442 299
                $mapping['isInverseSide'] = true;
1443 299
                $mapping['isOwningSide'] = false;
1444
            }
1445 599 View Code Duplication
            if (isset($mapping['repositoryMethod'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1446 67
                $mapping['isInverseSide'] = true;
1447 67
                $mapping['isOwningSide'] = false;
1448
            }
1449 599
            if (!isset($mapping['orphanRemoval'])) {
1450 574
                $mapping['orphanRemoval'] = false;
1451
            }
1452
        }
1453
1454 967
        if (!empty($mapping['prime']) && ($mapping['association'] !== self::REFERENCE_MANY || !$mapping['isInverseSide'])) {
1455
            throw MappingException::referencePrimersOnlySupportedForInverseReferenceMany($this->name, $mapping['fieldName']);
1456
        }
1457
1458 967
        $this->applyStorageStrategy($mapping);
1459
1460 966
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
1461 966
        if (isset($mapping['association'])) {
1462 752
            $this->associationMappings[$mapping['fieldName']] = $mapping;
1463
        }
1464
1465 966
        return $mapping;
1466
    }
1467
1468
    /**
1469
     * Validates the storage strategy of a mapping for consistency
1470
     * @param array $mapping
1471
     * @throws \Doctrine\ODM\MongoDB\Mapping\MappingException
1472
     */
1473 967
    private function applyStorageStrategy(array &$mapping)
1474
    {
1475 967
        if (! isset($mapping['type']) || isset($mapping['id'])) {
1476 949
            return;
1477
        }
1478
1479
        switch (true) {
1480 929
            case $mapping['type'] == 'int':
1481 928
            case $mapping['type'] == 'float':
1482 928
            case $mapping['type'] == 'increment':
1483 340
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1484 340
                $allowedStrategies = [self::STORAGE_STRATEGY_SET, self::STORAGE_STRATEGY_INCREMENT];
1485 340
                break;
1486
1487 927
            case $mapping['type'] == 'many':
1488 622
                $defaultStrategy = CollectionHelper::DEFAULT_STRATEGY;
1489
                $allowedStrategies = [
1490 622
                    self::STORAGE_STRATEGY_PUSH_ALL,
1491 622
                    self::STORAGE_STRATEGY_ADD_TO_SET,
1492 622
                    self::STORAGE_STRATEGY_SET,
1493 622
                    self::STORAGE_STRATEGY_SET_ARRAY,
1494 622
                    self::STORAGE_STRATEGY_ATOMIC_SET,
1495 622
                    self::STORAGE_STRATEGY_ATOMIC_SET_ARRAY,
1496
                ];
1497 622
                break;
1498
1499
            default:
1500 915
                $defaultStrategy = self::STORAGE_STRATEGY_SET;
1501 915
                $allowedStrategies = [self::STORAGE_STRATEGY_SET];
1502
        }
1503
1504 929
        if (! isset($mapping['strategy'])) {
1505 918
            $mapping['strategy'] = $defaultStrategy;
1506
        }
1507
1508 929
        if (! in_array($mapping['strategy'], $allowedStrategies)) {
1509
            throw MappingException::invalidStorageStrategy($this->name, $mapping['fieldName'], $mapping['type'], $mapping['strategy']);
1510
        }
1511
1512 929
        if (isset($mapping['reference']) && $mapping['type'] === 'many' && $mapping['isOwningSide']
1513 929
            && ! empty($mapping['sort']) && ! CollectionHelper::usesSet($mapping['strategy'])) {
1514 1
            throw MappingException::referenceManySortMustNotBeUsedWithNonSetCollectionStrategy($this->name, $mapping['fieldName'], $mapping['strategy']);
1515
        }
1516 928
    }
1517
1518
    /**
1519
     * Map a MongoGridFSFile.
1520
     *
1521
     * @param array $mapping The mapping information.
1522
     */
1523
    public function mapFile(array $mapping)
1524
    {
1525
        $mapping['file'] = true;
1526
        $mapping['type'] = 'file';
1527
        $this->mapField($mapping);
1528
    }
1529
1530
    /**
1531
     * Map a single embedded document.
1532
     *
1533
     * @param array $mapping The mapping information.
1534
     */
1535 6
    public function mapOneEmbedded(array $mapping)
1536
    {
1537 6
        $mapping['embedded'] = true;
1538 6
        $mapping['type'] = 'one';
1539 6
        $this->mapField($mapping);
1540 5
    }
1541
1542
    /**
1543
     * Map a collection of embedded documents.
1544
     *
1545
     * @param array $mapping The mapping information.
1546
     */
1547 5
    public function mapManyEmbedded(array $mapping)
1548
    {
1549 5
        $mapping['embedded'] = true;
1550 5
        $mapping['type'] = 'many';
1551 5
        $this->mapField($mapping);
1552 5
    }
1553
1554
    /**
1555
     * Map a single document reference.
1556
     *
1557
     * @param array $mapping The mapping information.
1558
     */
1559 8
    public function mapOneReference(array $mapping)
1560
    {
1561 8
        $mapping['reference'] = true;
1562 8
        $mapping['type'] = 'one';
1563 8
        $this->mapField($mapping);
1564 8
    }
1565
1566
    /**
1567
     * Map a collection of document references.
1568
     *
1569
     * @param array $mapping The mapping information.
1570
     */
1571 8
    public function mapManyReference(array $mapping)
1572
    {
1573 8
        $mapping['reference'] = true;
1574 8
        $mapping['type'] = 'many';
1575 8
        $this->mapField($mapping);
1576 8
    }
1577
1578
    /**
1579
     * INTERNAL:
1580
     * Adds a field mapping without completing/validating it.
1581
     * This is mainly used to add inherited field mappings to derived classes.
1582
     *
1583
     * @param array $fieldMapping
1584
     */
1585 129
    public function addInheritedFieldMapping(array $fieldMapping)
1586
    {
1587 129
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
1588
1589 129
        if (isset($fieldMapping['association'])) {
1590 77
            $this->associationMappings[$fieldMapping['fieldName']] = $fieldMapping;
1591
        }
1592 129
    }
1593
1594
    /**
1595
     * INTERNAL:
1596
     * Adds an association mapping without completing/validating it.
1597
     * This is mainly used to add inherited association mappings to derived classes.
1598
     *
1599
     * @param array $mapping
1600
     *
1601
     * @return void
1602
     *
1603
     * @throws MappingException
1604
     */
1605 78
    public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
1606
    {
1607 78
        $this->associationMappings[$mapping['fieldName']] = $mapping;
1608 78
    }
1609
1610
    /**
1611
     * Checks whether the class has a mapped association with the given field name.
1612
     *
1613
     * @param string $fieldName
1614
     * @return boolean
1615
     */
1616 17
    public function hasReference($fieldName)
1617
    {
1618 17
        return isset($this->fieldMappings[$fieldName]['reference']);
1619
    }
1620
1621
    /**
1622
     * Checks whether the class has a mapped embed with the given field name.
1623
     *
1624
     * @param string $fieldName
1625
     * @return boolean
1626
     */
1627 5
    public function hasEmbed($fieldName)
1628
    {
1629 5
        return isset($this->fieldMappings[$fieldName]['embedded']);
1630
    }
1631
1632
    /**
1633
     * {@inheritDoc}
1634
     *
1635
     * Checks whether the class has a mapped association (embed or reference) with the given field name.
1636
     */
1637 7
    public function hasAssociation($fieldName)
1638
    {
1639 7
        return $this->hasReference($fieldName) || $this->hasEmbed($fieldName);
1640
    }
1641
1642
    /**
1643
     * {@inheritDoc}
1644
     *
1645
     * Checks whether the class has a mapped reference or embed for the specified field and
1646
     * is a single valued association.
1647
     */
1648
    public function isSingleValuedAssociation($fieldName)
1649
    {
1650
        return $this->isSingleValuedReference($fieldName) || $this->isSingleValuedEmbed($fieldName);
1651
    }
1652
1653
    /**
1654
     * {@inheritDoc}
1655
     *
1656
     * Checks whether the class has a mapped reference or embed for the specified field and
1657
     * is a collection valued association.
1658
     */
1659
    public function isCollectionValuedAssociation($fieldName)
1660
    {
1661
        return $this->isCollectionValuedReference($fieldName) || $this->isCollectionValuedEmbed($fieldName);
1662
    }
1663
1664
    /**
1665
     * Checks whether the class has a mapped association for the specified field
1666
     * and if yes, checks whether it is a single-valued association (to-one).
1667
     *
1668
     * @param string $fieldName
1669
     * @return boolean TRUE if the association exists and is single-valued, FALSE otherwise.
1670
     */
1671
    public function isSingleValuedReference($fieldName)
1672
    {
1673
        return isset($this->fieldMappings[$fieldName]['association']) &&
1674
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_ONE;
1675
    }
1676
1677
    /**
1678
     * Checks whether the class has a mapped association for the specified field
1679
     * and if yes, checks whether it is a collection-valued association (to-many).
1680
     *
1681
     * @param string $fieldName
1682
     * @return boolean TRUE if the association exists and is collection-valued, FALSE otherwise.
1683
     */
1684
    public function isCollectionValuedReference($fieldName)
1685
    {
1686
        return isset($this->fieldMappings[$fieldName]['association']) &&
1687
            $this->fieldMappings[$fieldName]['association'] === self::REFERENCE_MANY;
1688
    }
1689
1690
    /**
1691
     * Checks whether the class has a mapped embedded document for the specified field
1692
     * and if yes, checks whether it is a single-valued association (to-one).
1693
     *
1694
     * @param string $fieldName
1695
     * @return boolean TRUE if the association exists and is single-valued, FALSE otherwise.
1696
     */
1697
    public function isSingleValuedEmbed($fieldName)
1698
    {
1699
        return isset($this->fieldMappings[$fieldName]['association']) &&
1700
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_ONE;
1701
    }
1702
1703
    /**
1704
     * Checks whether the class has a mapped embedded document for the specified field
1705
     * and if yes, checks whether it is a collection-valued association (to-many).
1706
     *
1707
     * @param string $fieldName
1708
     * @return boolean TRUE if the association exists and is collection-valued, FALSE otherwise.
1709
     */
1710
    public function isCollectionValuedEmbed($fieldName)
1711
    {
1712
        return isset($this->fieldMappings[$fieldName]['association']) &&
1713
            $this->fieldMappings[$fieldName]['association'] === self::EMBED_MANY;
1714
    }
1715
1716
    /**
1717
     * Sets the ID generator used to generate IDs for instances of this class.
1718
     *
1719
     * @param \Doctrine\ODM\MongoDB\Id\AbstractIdGenerator $generator
1720
     */
1721 868
    public function setIdGenerator($generator)
1722
    {
1723 868
        $this->idGenerator = $generator;
1724 868
    }
1725
1726
    /**
1727
     * Casts the identifier to its portable PHP type.
1728
     *
1729
     * @param mixed $id
1730
     * @return mixed $id
1731
     */
1732 665
    public function getPHPIdentifierValue($id)
1733
    {
1734 665
        $idType = $this->fieldMappings[$this->identifier]['type'];
1735 665
        return Type::getType($idType)->convertToPHPValue($id);
1736
    }
1737
1738
    /**
1739
     * Casts the identifier to its database type.
1740
     *
1741
     * @param mixed $id
1742
     * @return mixed $id
1743
     */
1744 737
    public function getDatabaseIdentifierValue($id)
1745
    {
1746 737
        $idType = $this->fieldMappings[$this->identifier]['type'];
1747 737
        return Type::getType($idType)->convertToDatabaseValue($id);
1748
    }
1749
1750
    /**
1751
     * Sets the document identifier of a document.
1752
     *
1753
     * The value will be converted to a PHP type before being set.
1754
     *
1755
     * @param object $document
1756
     * @param mixed $id
1757
     */
1758 591
    public function setIdentifierValue($document, $id)
1759
    {
1760 591
        $id = $this->getPHPIdentifierValue($id);
1761 591
        $this->reflFields[$this->identifier]->setValue($document, $id);
1762 591
    }
1763
1764
    /**
1765
     * Gets the document identifier as a PHP type.
1766
     *
1767
     * @param object $document
1768
     * @return mixed $id
1769
     */
1770 682
    public function getIdentifierValue($document)
1771
    {
1772 682
        return $this->reflFields[$this->identifier]->getValue($document);
1773
    }
1774
1775
    /**
1776
     * {@inheritDoc}
1777
     *
1778
     * Since MongoDB only allows exactly one identifier field this is a proxy
1779
     * to {@see getIdentifierValue()} and returns an array with the identifier
1780
     * field as a key.
1781
     */
1782
    public function getIdentifierValues($object)
1783
    {
1784
        return array($this->identifier => $this->getIdentifierValue($object));
1785
    }
1786
1787
    /**
1788
     * Get the document identifier object as a database type.
1789
     *
1790
     * @param object $document
1791
     *
1792
     * @return \MongoId $id The MongoID object.
1793
     */
1794 36
    public function getIdentifierObject($document)
1795
    {
1796 36
        return $this->getDatabaseIdentifierValue($this->getIdentifierValue($document));
1797
    }
1798
1799
    /**
1800
     * Sets the specified field to the specified value on the given document.
1801
     *
1802
     * @param object $document
1803
     * @param string $field
1804
     * @param mixed $value
1805
     */
1806 11
    public function setFieldValue($document, $field, $value)
1807
    {
1808 11
        if ($document instanceof Proxy && ! $document->__isInitialized()) {
1809
            //property changes to an uninitialized proxy will not be tracked or persisted,
1810
            //so the proxy needs to be loaded first.
1811 1
            $document->__load();
1812
        }
1813
1814 11
        $this->reflFields[$field]->setValue($document, $value);
1815 11
    }
1816
1817
    /**
1818
     * Gets the specified field's value off the given document.
1819
     *
1820
     * @param object $document
1821
     * @param string $field
1822
     *
1823
     * @return mixed
1824
     */
1825 31
    public function getFieldValue($document, $field)
1826
    {
1827 31
        if ($document instanceof Proxy && $field !== $this->identifier && ! $document->__isInitialized()) {
1828 1
            $document->__load();
1829
        }
1830
1831 31
        return $this->reflFields[$field]->getValue($document);
1832
    }
1833
1834
    /**
1835
     * Gets the mapping of a field.
1836
     *
1837
     * @param string $fieldName  The field name.
1838
     *
1839
     * @return array  The field mapping.
1840
     *
1841
     * @throws MappingException if the $fieldName is not found in the fieldMappings array
1842
     */
1843 201
    public function getFieldMapping($fieldName)
1844
    {
1845 201
        if ( ! isset($this->fieldMappings[$fieldName])) {
1846 6
            throw MappingException::mappingNotFound($this->name, $fieldName);
1847
        }
1848 199
        return $this->fieldMappings[$fieldName];
1849
    }
1850
1851
    /**
1852
     * Gets mappings of fields holding embedded document(s).
1853
     *
1854
     * @return array of field mappings
1855
     */
1856 634
    public function getEmbeddedFieldsMappings()
1857
    {
1858 634
        return array_filter(
1859 634
            $this->associationMappings,
1860
            function($assoc) { return ! empty($assoc['embedded']); }
1861
        );
1862
    }
1863
1864
    /**
1865
     * Gets the field mapping by its DB name.
1866
     * E.g. it returns identifier's mapping when called with _id.
1867
     *
1868
     * @param string $dbFieldName
1869
     *
1870
     * @return array
1871
     * @throws MappingException
1872
     */
1873 9
    public function getFieldMappingByDbFieldName($dbFieldName)
1874
    {
1875 9
        foreach ($this->fieldMappings as $mapping) {
1876 9
            if ($mapping['name'] == $dbFieldName) {
1877 9
                return $mapping;
1878
            }
1879
        }
1880
1881
        throw MappingException::mappingNotFoundByDbName($this->name, $dbFieldName);
1882
    }
1883
1884
    /**
1885
     * Check if the field is not null.
1886
     *
1887
     * @param string $fieldName  The field name
1888
     *
1889
     * @return boolean  TRUE if the field is not null, FALSE otherwise.
1890
     */
1891 1
    public function isNullable($fieldName)
1892
    {
1893 1
        $mapping = $this->getFieldMapping($fieldName);
1894 1
        if ($mapping !== false) {
1895 1
            return isset($mapping['nullable']) && $mapping['nullable'] == true;
1896
        }
1897
        return false;
1898
    }
1899
1900
    /**
1901
     * Checks whether the document has a discriminator field and value configured.
1902
     *
1903
     * @return boolean
1904
     */
1905 539
    public function hasDiscriminator()
1906
    {
1907 539
        return isset($this->discriminatorField, $this->discriminatorValue);
1908
    }
1909
1910
    /**
1911
     * Sets the type of Id generator to use for the mapped class.
1912
     *
1913
     * @param string $generatorType Generator type.
1914
     */
1915 379
    public function setIdGeneratorType($generatorType)
1916
    {
1917 379
        $this->generatorType = $generatorType;
1918 379
    }
1919
1920
    /**
1921
     * Sets the Id generator options.
1922
     *
1923
     * @param array $generatorOptions Generator options.
1924
     */
1925
    public function setIdGeneratorOptions($generatorOptions)
1926
    {
1927
        $this->generatorOptions = $generatorOptions;
1928
    }
1929
1930
    /**
1931
     * @return boolean
1932
     */
1933 641
    public function isInheritanceTypeNone()
1934
    {
1935 641
        return $this->inheritanceType == self::INHERITANCE_TYPE_NONE;
1936
    }
1937
1938
    /**
1939
     * Checks whether the mapped class uses the SINGLE_COLLECTION inheritance mapping strategy.
1940
     *
1941
     * @return boolean
1942
     */
1943 372
    public function isInheritanceTypeSingleCollection()
1944
    {
1945 372
        return $this->inheritanceType == self::INHERITANCE_TYPE_SINGLE_COLLECTION;
1946
    }
1947
1948
    /**
1949
     * Checks whether the mapped class uses the COLLECTION_PER_CLASS inheritance mapping strategy.
1950
     *
1951
     * @return boolean
1952
     */
1953
    public function isInheritanceTypeCollectionPerClass()
1954
    {
1955
        return $this->inheritanceType == self::INHERITANCE_TYPE_COLLECTION_PER_CLASS;
1956
    }
1957
1958
    /**
1959
     * Sets the mapped subclasses of this class.
1960
     *
1961
     * @param string[] $subclasses The names of all mapped subclasses.
1962
     */
1963 2
    public function setSubclasses(array $subclasses)
1964
    {
1965 2
        foreach ($subclasses as $subclass) {
1966 2
            if (strpos($subclass, '\\') === false && strlen($this->namespace)) {
1967 1
                $this->subClasses[] = $this->namespace . '\\' . $subclass;
1968
            } else {
1969 2
                $this->subClasses[] = $subclass;
1970
            }
1971
        }
1972 2
    }
1973
1974
    /**
1975
     * Sets the parent class names.
1976
     * Assumes that the class names in the passed array are in the order:
1977
     * directParent -> directParentParent -> directParentParentParent ... -> root.
1978
     *
1979
     * @param string[] $classNames
1980
     */
1981 926
    public function setParentClasses(array $classNames)
1982
    {
1983 926
        $this->parentClasses = $classNames;
1984
1985 926
        if (count($classNames) > 0) {
1986 113
            $this->rootDocumentName = array_pop($classNames);
1987
        }
1988 926
    }
1989
1990
    /**
1991
     * Checks whether the class will generate a new \MongoId instance for us.
1992
     *
1993
     * @return boolean TRUE if the class uses the AUTO generator, FALSE otherwise.
1994
     */
1995
    public function isIdGeneratorAuto()
1996
    {
1997
        return $this->generatorType == self::GENERATOR_TYPE_AUTO;
1998
    }
1999
2000
    /**
2001
     * Checks whether the class will use a collection to generate incremented identifiers.
2002
     *
2003
     * @return boolean TRUE if the class uses the INCREMENT generator, FALSE otherwise.
2004
     */
2005
    public function isIdGeneratorIncrement()
2006
    {
2007
        return $this->generatorType == self::GENERATOR_TYPE_INCREMENT;
2008
    }
2009
2010
    /**
2011
     * Checks whether the class will generate a uuid id.
2012
     *
2013
     * @return boolean TRUE if the class uses the UUID generator, FALSE otherwise.
2014
     */
2015
    public function isIdGeneratorUuid()
2016
    {
2017
        return $this->generatorType == self::GENERATOR_TYPE_UUID;
2018
    }
2019
2020
    /**
2021
     * Checks whether the class uses no id generator.
2022
     *
2023
     * @return boolean TRUE if the class does not use any id generator, FALSE otherwise.
2024
     */
2025
    public function isIdGeneratorNone()
2026
    {
2027
        return $this->generatorType == self::GENERATOR_TYPE_NONE;
2028
    }
2029
2030
    /**
2031
     * Sets the version field mapping used for versioning. Sets the default
2032
     * value to use depending on the column type.
2033
     *
2034
     * @param array $mapping   The version field mapping array
2035
     *
2036
     * @throws LockException
2037
     */
2038 102
    public function setVersionMapping(array &$mapping)
2039
    {
2040 102
        if ($mapping['type'] !== 'int' && $mapping['type'] !== 'date') {
2041 1
            throw LockException::invalidVersionFieldType($mapping['type']);
2042
        }
2043
2044 101
        $this->isVersioned  = true;
2045 101
        $this->versionField = $mapping['fieldName'];
2046 101
    }
2047
2048
    /**
2049
     * Sets whether this class is to be versioned for optimistic locking.
2050
     *
2051
     * @param boolean $bool
2052
     */
2053 373
    public function setVersioned($bool)
2054
    {
2055 373
        $this->isVersioned = $bool;
2056 373
    }
2057
2058
    /**
2059
     * Sets the name of the field that is to be used for versioning if this class is
2060
     * versioned for optimistic locking.
2061
     *
2062
     * @param string $versionField
2063
     */
2064 373
    public function setVersionField($versionField)
2065
    {
2066 373
        $this->versionField = $versionField;
2067 373
    }
2068
2069
    /**
2070
     * Sets the version field mapping used for versioning. Sets the default
2071
     * value to use depending on the column type.
2072
     *
2073
     * @param array $mapping   The version field mapping array
2074
     *
2075
     * @throws \Doctrine\ODM\MongoDB\LockException
2076
     */
2077 27
    public function setLockMapping(array &$mapping)
2078
    {
2079 27
        if ($mapping['type'] !== 'int') {
2080 1
            throw LockException::invalidLockFieldType($mapping['type']);
2081
        }
2082
2083 26
        $this->isLockable = true;
2084 26
        $this->lockField = $mapping['fieldName'];
2085 26
    }
2086
2087
    /**
2088
     * Sets whether this class is to allow pessimistic locking.
2089
     *
2090
     * @param boolean $bool
2091
     */
2092
    public function setLockable($bool)
2093
    {
2094
        $this->isLockable = $bool;
2095
    }
2096
2097
    /**
2098
     * Sets the name of the field that is to be used for storing whether a document
2099
     * is currently locked or not.
2100
     *
2101
     * @param string $lockField
2102
     */
2103
    public function setLockField($lockField)
2104
    {
2105
        $this->lockField = $lockField;
2106
    }
2107
2108
    /**
2109
     * Marks this class as read only, no change tracking is applied to it.
2110
     */
2111 9
    public function markReadOnly()
2112
    {
2113 9
        $this->isReadOnly = true;
2114 9
    }
2115
2116
    /**
2117
     * {@inheritDoc}
2118
     */
2119
    public function getFieldNames()
2120
    {
2121
        return array_keys($this->fieldMappings);
2122
    }
2123
2124
    /**
2125
     * {@inheritDoc}
2126
     */
2127
    public function getAssociationNames()
2128
    {
2129
        return array_keys($this->associationMappings);
2130
    }
2131
2132
    /**
2133
     * {@inheritDoc}
2134
     */
2135 22
    public function getTypeOfField($fieldName)
2136
    {
2137 22
        return isset($this->fieldMappings[$fieldName]) ?
2138 22
            $this->fieldMappings[$fieldName]['type'] : null;
2139
    }
2140
2141
    /**
2142
     * {@inheritDoc}
2143
     */
2144 6
    public function getAssociationTargetClass($assocName)
2145
    {
2146 6
        if ( ! isset($this->associationMappings[$assocName])) {
2147 3
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
2148
        }
2149
2150 3
        return $this->associationMappings[$assocName]['targetDocument'];
2151
    }
2152
2153
    /**
2154
     * Retrieve the collectionClass associated with an association
2155
     *
2156
     * @param string $assocName
2157
     */
2158 2
    public function getAssociationCollectionClass($assocName)
2159
    {
2160 2
        if ( ! isset($this->associationMappings[$assocName])) {
2161
            throw new InvalidArgumentException("Association name expected, '" . $assocName . "' is not an association.");
2162
        }
2163
2164 2
        if ( ! array_key_exists('collectionClass', $this->associationMappings[$assocName])) {
2165
            throw new InvalidArgumentException("collectionClass can only be applied to 'embedMany' and 'referenceMany' associations.");
2166
        }
2167
2168 2
        return $this->associationMappings[$assocName]['collectionClass'];
2169
    }
2170
2171
    /**
2172
     * {@inheritDoc}
2173
     */
2174
    public function isAssociationInverseSide($fieldName)
2175
    {
2176
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
2177
    }
2178
2179
    /**
2180
     * {@inheritDoc}
2181
     */
2182
    public function getAssociationMappedByTargetField($fieldName)
2183
    {
2184
        throw new \BadMethodCallException(__METHOD__ . '() is not implemented yet.');
2185
    }
2186
}
2187