ClassMetadata   F
last analyzed

Complexity

Total Complexity 100

Size/Duplication

Total Lines 850
Duplicated Lines 4.59 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 0
Metric Value
wmc 100
lcom 1
cbo 4
dl 39
loc 850
rs 1.75
c 0
b 0
f 0

46 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
B deriveChildMetadata() 0 33 8
F __sleep() 0 55 11
B wakeupReflection() 19 38 8
A newInstance() 0 4 1
A getReflectionClass() 0 7 2
A getReflectionProperties() 0 4 1
A getReflectionProperty() 0 4 1
A setIdentifierValue() 0 4 1
A getIdentifierValue() 0 4 1
A getIdentifierValues() 0 4 1
A setFieldValue() 0 4 1
A getFieldValue() 0 4 1
A isIdentifier() 0 4 2
A setIdentifier() 0 7 2
A getIdentifier() 0 4 1
A getIdentifierFieldNames() 0 4 1
A hasField() 0 4 1
A setCustomRepositoryClass() 0 4 1
A getName() 0 4 1
A getNamespace() 0 4 1
A mapAttachments() 0 9 3
A mapEmbedded() 0 6 1
B mapField() 0 26 6
A validateAndCompleteFieldMapping() 0 14 6
A validateAndCompleteReferenceMapping() 0 7 5
A validateAndCompleteAssociationMapping() 0 8 1
A mapManyToOne() 11 11 1
A mapManyToMany() 9 9 1
A checkAndStoreIndexMapping() 0 9 3
A storeAssociationMapping() 0 5 1
A getFieldNames() 0 4 1
A getFieldMapping() 0 7 2
A getTypeOfField() 0 5 2
A hasAssociation() 0 4 1
A isCollectionValuedAssociation() 0 5 2
A isSingleValuedAssociation() 0 5 2
A getAssociationNames() 0 4 1
A getAssociationTargetClass() 0 7 2
A getAssociationMappedByTargetField() 0 4 1
A isAssociationInverseSide() 0 4 2
A isInheritedField() 0 4 1
A isInheritedAssociation() 0 4 1
A setParentClasses() 0 8 2
A markInheritanceRoot() 0 7 2
A initializeReflection() 0 9 2

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

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

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

1
<?php
2
3
namespace Doctrine\ODM\CouchDB\Mapping;
4
5
use Doctrine\Common\Persistence\Mapping\ClassMetadata AS IClassMetadata,
6
    Doctrine\Instantiator\Instantiator,
7
    ReflectionClass,
8
    ReflectionProperty;
9
10
/**
11
 * Metadata class
12
 *
13
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
14
 * @link        www.doctrine-project.com
15
 * @since       1.0
16
 * @author      Benjamin Eberlei <[email protected]>
17
 * @author      Lukas Kahwe Smith <[email protected]>
18
 */
19
class ClassMetadata implements IClassMetadata
20
{
21
    const IDGENERATOR_UUID = 1;
22
    const IDGENERATOR_ASSIGNED = 2;
23
24
    const TO_ONE = 5;
25
    const TO_MANY = 10;
26
    const ONE_TO_ONE = 1;
27
    const ONE_TO_MANY = 2;
28
    const MANY_TO_ONE = 4;
29
    const MANY_TO_MANY = 8;
30
31
    const CASCADE_PERSIST = 1;
32
    const CASCADE_REMOVE  = 2;
33
    const CASCADE_MERGE   = 4;
34
    const CASCADE_DETACH  = 8;
35
    const CASCADE_REFRESH = 16;
36
    const CASCADE_ALL     = 31;
37
38
    public $idGenerator = self::IDGENERATOR_UUID;
39
40
    /**
41
     * READ-ONLY: The field name of the document identifier.
42
     */
43
    public $identifier;
44
45
    /**
46
     * READ-ONLY: The name of the document class.
47
     */
48
    public $name;
49
50
    /**
51
     * READ-ONLY: The root document class name.
52
     */
53
    public $rootDocumentName;
54
55
    /**
56
     * READ-ONLY: Is this entity in an inheritance hierachy?
57
     */
58
    public $inInheritanceHierachy = false;
59
60
    /**
61
     * READ-ONLY: a list of all parent classes.
62
     */
63
    public $parentClasses = array();
64
65
    /**
66
     * READ-ONLY: The namespace the document class is contained in.
67
     *
68
     * @var string
69
     * @todo Not really needed. Usage could be localized.
70
     */
71
    public $namespace;
72
73
    /**
74
     * The name of the custom repository class used for the document class.
75
     * (Optional).
76
     *
77
     * @var string
78
     */
79
    public $customRepositoryClassName;
80
81
    /**
82
     * READ-ONLY: The field mappings of the class.
83
     * Keys are field names and values are mapping definitions.
84
     *
85
     * The mapping definition array has the following values:
86
     *
87
     * - <b>fieldName</b> (string)
88
     * The name of the field in the Document.
89
     *
90
     * - <b>id</b> (boolean, optional)
91
     * Marks the field as the primary key of the document. Multiple fields of an
92
     * document can have the id attribute, forming a composite key.
93
     *
94
     * @var array
95
     */
96
    public $fieldMappings = array();
97
98
    /**
99
     * An array of indexed fields, accessible through a generic view shipped with Doctrine.
100
     *
101
     * @var array
102
     */
103
    public $indexes = array();
104
105
    /**
106
     * Is this class indexed? If yes, then a findAll() query can be executed for this type.
107
     *
108
     * @var bool
109
     */
110
    public $indexed = false;
111
112
    /**
113
     * An array of json result-key-names to field-names
114
     *
115
     * @var array
116
     */
117
    public $jsonNames = array();
118
119
    /**
120
     * READ-ONLY: Array of fields to also load with a given method.
121
     *
122
     * @var array
123
     */
124
    public $alsoLoadMethods = array();
125
126
    /**
127
     * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
128
     *
129
     * @var boolean
130
     */
131
    public $isMappedSuperclass = false;
132
133
    /**
134
     * READ-ONLY: Whether this class describes the mapping of a embedded document.
135
     *
136
     * @var boolean
137
     */
138
    public $isEmbeddedDocument = false;
139
140
    /**
141
     * READ-ONLY: Wheather the document or embedded document is read-only and will be skipped in change-tracking.
142
     *
143
     * This should be set to true for value objects, for example attachments. Replacing the reference with a new
144
     * value object will trigger an update.
145
     *
146
     * @var bool
147
     */
148
    public $isReadOnly = false;
149
150
    /**
151
     * READ-ONLY
152
     *
153
     * @var array
154
     */
155
    public $associationsMappings = array();
156
157
    /**
158
     * CouchDB documents are always versioned, this flag determines if this version is exposed to the userland.
159
     *
160
     * @var bool
161
     */
162
    public $isVersioned = false;
163
164
    /**
165
     * Version Field stores the CouchDB Revision
166
     *
167
     * @var string
168
     */
169
    public $versionField = null;
170
171
    /**
172
     * @var bool
173
     */
174
    public $hasAttachments = false;
175
176
    /**
177
     * Field that stores the attachments as a key->value array of file-names to attachment objects.
178
     *
179
     * @var string
180
     */
181
    public $attachmentField = null;
182
183
    /**
184
     * If in an inheritance scenario the attachment field is on a super class, this is its name.
185
     *
186
     * @var string|null
187
     */
188
    public $attachmentDeclaredClass = null;
189
190
    /**
191
     * The ReflectionClass instance of the mapped class.
192
     *
193
     * @var ReflectionClass
194
     */
195
    public $reflClass;
196
197
    /**
198
     * The ReflectionProperty instances of the mapped class.
199
     *
200
     * @var array
201
     */
202
    public $reflFields = array();
203
204
    /**
205
     * @var \Doctrine\Instantiator\InstantiatorInterface
206
     */
207
    private $instantiator;
208
209
    /**
210
     * Initializes a new ClassMetadata instance that will hold the object-document mapping
211
     * metadata of the class with the given name.
212
     *
213
     * @param string $documentName The name of the document class the new instance is used for.
214
     */
215
    public function __construct($documentName)
216
    {
217
        $this->name = $documentName;
218
        $this->rootDocumentName = $documentName;
219
        $this->instantiator = new Instantiator();
220
    }
221
222
    /**
223
     * Used to derive a class metadata of the current instance for a mapped child class.
224
     *
225
     * @param  ClassMetadata $child
226
     * @throws \InvalidArgumentException
227
     */
228
    public function deriveChildMetadata($child)
229
    {
230
        if (!is_subclass_of($child->name, $this->name)) {
231
            throw new \InvalidArgumentException("Only child class names of '".$this->name."' are valid values.");
232
        }
233
234
        $child->isMappedSuperclass = false;
235
        $child->isEmbeddedDocument = false;
236
237
        foreach ($this->fieldMappings AS $fieldName => $fieldMapping) {
238
            $child->fieldMappings[$fieldName] = $fieldMapping;
239
240
            if (!isset($fieldMapping['declared'])) {
241
                $child->fieldMappings[$fieldName]['declared'] = $this->name;
242
            }
243
        }
244
245
        foreach ($this->associationsMappings AS $assocName => $assocMapping) {
246
            $child->associationsMappings[$assocName] = $assocMapping;
247
248
            if (!isset($assocMapping['declared'])) {
249
                $child->associationsMappings[$assocName]['declared'] = $this->name;
250
            }
251
        }
252
253
        if ($this->attachmentField) {
254
            $child->attachmentField = $this->attachmentField;
255
256
            if (!$child->attachmentDeclaredClass) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $child->attachmentDeclaredClass of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
257
                $child->attachmentDeclaredClass = $this->name;
258
            }
259
        }
260
    }
261
262
    /**
263
     * Determines which fields get serialized.
264
     *
265
     * It is only serialized what is necessary for best unserialization performance.
266
     * That means any metadata properties that are not set or empty or simply have
267
     * their default value are NOT serialized.
268
     *
269
     * Parts that are also NOT serialized because they can not be properly unserialized:
270
     *      - reflClass (ReflectionClass)
271
     *      - reflFields (ReflectionProperty array)
272
     *
273
     * @return array The names of all the fields that should be serialized.
274
     */
275
    public function __sleep()
276
    {
277
        // This metadata is always serialized/cached.
278
        $serialized = array(
279
            'name',
280
            'associationsMappings',
281
            'fieldMappings',
282
            'jsonNames',
283
            'idGenerator',
284
            'identifier',
285
            'rootDocumentName',
286
        );
287
288
        if ($this->inInheritanceHierachy) {
289
            $serialized[] = 'inInheritanceHierachy';
290
        }
291
292
        if ($this->parentClasses) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->parentClasses of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
293
            $serialized[] = 'parentClasses';
294
        }
295
296
        if ($this->isVersioned) {
297
            $serialized[] = 'isVersioned';
298
            $serialized[] = 'versionField';
299
        }
300
301
        if ($this->customRepositoryClassName) {
302
            $serialized[] = 'customRepositoryClassName';
303
        }
304
305
        if ($this->hasAttachments) {
306
            $serialized[] = 'hasAttachments';
307
            $serialized[] = 'attachmentField';
308
            if ($this->attachmentDeclaredClass) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->attachmentDeclaredClass of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
309
                $serialized[] = 'attachmentDeclaredClass';
310
            }
311
        }
312
313
        if ($this->isReadOnly) {
314
            $serialized[] = 'isReadOnly';
315
        }
316
317
        if ($this->isMappedSuperclass) {
318
            $serialized[] = 'isMappedSuperclass';
319
        }
320
321
        if ($this->indexed) {
322
            $serialized[] = 'indexed';
323
        }
324
        if ($this->indexes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->indexes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
325
            $serialized[] = 'indexes';
326
        }
327
328
        return $serialized;
329
    }
330
331
    /**
332
     * Restores some state that can not be serialized/unserialized.
333
     */
334
    public function wakeupReflection($reflService)
335
    {
336
        // Restore ReflectionClass and properties
337
        $this->reflClass    = $reflService->getClass($this->name);
338
        $this->namespace    = $reflService->getClassNamespace($this->name);
339
        $this->instantiator = $this->instantiator ?: new Instantiator();
340
341 View Code Duplication
        foreach ($this->fieldMappings as $field => $mapping) {
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...
342
            if (isset($mapping['declared'])) {
343
                $reflField = new \ReflectionProperty($mapping['declared'], $field);
344
            } else {
345
                $reflField = $this->reflClass->getProperty($field);
346
            }
347
            $reflField->setAccessible(true);
348
            $this->reflFields[$field] = $reflField;
349
        }
350
351 View Code Duplication
        foreach ($this->associationsMappings as $field => $mapping) {
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...
352
            if (isset($mapping['declared'])) {
353
                $reflField = new \ReflectionProperty($mapping['declared'], $field);
354
            } else {
355
                $reflField = $this->reflClass->getProperty($field);
356
            }
357
358
            $reflField->setAccessible(true);
359
            $this->reflFields[$field] = $reflField;
360
        }
361
362
        if ($this->hasAttachments) {
363
            if ($this->attachmentDeclaredClass) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->attachmentDeclaredClass of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
364
                $reflField = new \ReflectionProperty($this->attachmentDeclaredClass, $this->attachmentField);
365
            } else {
366
                $reflField = $this->reflClass->getProperty($this->attachmentField);
367
            }
368
            $reflField->setAccessible(true);
369
            $this->reflFields[$this->attachmentField] = $reflField;
370
        }
371
    }
372
373
    /**
374
     * Creates a new instance of the mapped class, without invoking the constructor.
375
     *
376
     * @return object
377
     */
378
    public function newInstance()
379
    {
380
        return $this->instantiator->instantiate($this->name);
381
    }
382
383
    /**
384
     * Gets the ReflectionClass instance of the mapped class.
385
     *
386
     * @return ReflectionClass
387
     */
388
    public function getReflectionClass()
389
    {
390
        if ( ! $this->reflClass) {
391
            $this->reflClass = new ReflectionClass($this->name);
392
        }
393
        return $this->reflClass;
394
    }
395
396
    /**
397
     * Gets the ReflectionPropertys of the mapped class.
398
     *
399
     * @return array An array of ReflectionProperty instances.
400
     */
401
    public function getReflectionProperties()
402
    {
403
        return $this->reflFields;
404
    }
405
406
    /**
407
     * Gets a ReflectionProperty for a specific field of the mapped class.
408
     *
409
     * @param string $name
410
     * @return ReflectionProperty
411
     */
412
    public function getReflectionProperty($name)
413
    {
414
        return $this->reflFields[$name];
415
    }
416
417
418
    /**
419
     * Sets the document identifier of a document.
420
     *
421
     * @param object $document
422
     * @param mixed $id
423
     */
424
    public function setIdentifierValue($document, $id)
425
    {
426
        $this->reflFields[$this->identifier]->setValue($document, $id);
427
    }
428
429
    /**
430
     * Gets the document identifier.
431
     *
432
     * @param object $document
433
     * @return string $id
434
     */
435
    public function getIdentifierValue($document)
436
    {
437
        return (string) $this->reflFields[$this->identifier]->getValue($document);
438
    }
439
440
    /**
441
     * Get identifier values of this document.
442
     *
443
     * Since CouchDB only allows exactly one identifier field this is a proxy
444
     * to {@see getIdentifierValue()} and returns an array with the identifier
445
     * field as a key.
446
     *
447
     * @param object $document
448
     * @return array
449
     */
450
    public function getIdentifierValues($document)
451
    {
452
        return array($this->identifier => $this->getIdentifierValue($document));
0 ignored issues
show
Bug Best Practice introduced by
The return type of return array($this->iden...ifierValue($document)); (array<string,string>) is incompatible with the return type declared by the interface Doctrine\Common\Persiste...ta::getIdentifierValues of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
453
    }
454
455
    /**
456
     * Sets the specified field to the specified value on the given document.
457
     *
458
     * @param object $document
459
     * @param string $field
460
     * @param mixed $value
461
     */
462
    public function setFieldValue($document, $field, $value)
463
    {
464
        $this->reflFields[$field]->setValue($document, $value);
465
    }
466
467
    /**
468
     * Gets the specified field's value off the given document.
469
     *
470
     * @param object $document
471
     * @param string $field
472
     */
473
    public function getFieldValue($document, $field)
474
    {
475
        return $this->reflFields[$field]->getValue($document);
476
    }
477
478
    /**
479
     * Checks whether a field is part of the identifier/primary key field(s).
480
     *
481
     * @param string $fieldName  The field name
482
     * @return boolean  TRUE if the field is part of the table identifier/primary key field(s),
483
     *                  FALSE otherwise.
484
     */
485
    public function isIdentifier($fieldName)
486
    {
487
        return $this->identifier === $fieldName ? true : false;
488
    }
489
490
    /**
491
     * INTERNAL:
492
     * Sets the mapped identifier field of this class.
493
     *
494
     * @param string $identifier
495
     * @throws MappingException
496
     */
497
    public function setIdentifier($identifier)
498
    {
499
        if ($this->isEmbeddedDocument) {
500
            throw new MappingException('EmbeddedDocument should not have id field');
501
        }
502
        $this->identifier = $identifier;
503
    }
504
505
    /**
506
     * Gets the mapped identifier field of this class.
507
     *
508
     * @return string $identifier
509
     */
510
    public function getIdentifier()
511
    {
512
        return $this->identifier;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->identifier; (string) is incompatible with the return type declared by the interface Doctrine\Common\Persiste...Metadata::getIdentifier of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
513
    }
514
515
    /**
516
     * Get identifier field names of this class.;
517
     *
518
     * Since CouchDB only allows exactly one identifier field this is a proxy
519
     * to {@see getIdentifier()} and returns an array.
520
     *
521
     * @return array
522
     */
523
    public function getIdentifierFieldNames()
524
    {
525
        return array($this->identifier);
526
    }
527
528
    /**
529
     * Checks whether the class has a (mapped) field with a certain name.
530
     *
531
     * @param $fieldName
532
     * @return boolean
533
     */
534
    public function hasField($fieldName)
535
    {
536
        return isset($this->fieldMappings[$fieldName]);
537
    }
538
539
    /**
540
     * Registers a custom repository class for the document class.
541
     *
542
     * @param string $repositoryClassName  The class name of the custom mapper.
543
     */
544
    public function setCustomRepositoryClass($repositoryClassName)
545
    {
546
        $this->customRepositoryClassName = $repositoryClassName;
547
    }
548
549
    /**
550
     * The name of this Document class.
551
     *
552
     * @return string $name The Document class name.
553
     */
554
    public function getName()
555
    {
556
        return $this->name;
557
    }
558
559
    /**
560
     * The namespace this Document class belongs to.
561
     *
562
     * @return string $namespace The namespace name.
563
     */
564
    public function getNamespace()
565
    {
566
        return $this->namespace;
567
    }
568
569
    /**
570
     * Set the field that will contain attachments of this document.
571
     *
572
     * @param string $fieldName
573
     * @throws MappingException
574
     */
575
    public function mapAttachments($fieldName)
576
    {
577
        if (isset($this->fieldMappings[$fieldName]) || isset($this->associationsMappings[$fieldName])) {
578
            throw MappingException::duplicateFieldMapping($this->name, $fieldName);
579
        }
580
581
        $this->hasAttachments = true;
582
        $this->attachmentField = $fieldName;
583
    }
584
585
    /**
586
     * Map an embedded object
587
     *
588
     * - fieldName - The name of the property/field on the mapped php class
589
     * - jsonName - JSON key name of this field in CouchDB.
590
     * - targetDocument - Name of the target document
591
     * - embedded - one or many embedded objects?
592
     *
593
     * @param array $mapping
594
     */
595
    public function mapEmbedded(array $mapping)
596
    {
597
        $mapping = $this->validateAndCompleteReferenceMapping($mapping);
598
599
        $this->mapField($mapping);
600
    }
601
602
    /**
603
     * Map a field.
604
     *
605
     * - type - The Doctrine Type of this field.
606
     * - fieldName - The name of the property/field on the mapped php class
607
     * - jsonName - JSON key name of this field in CouchDB.
608
     * - name - The JSON key of this field in the CouchDB document
609
     * - id - True for an ID field.
610
     * - strategy - ID Generator strategy when the field is an id-field.
611
     * - indexed - Is this field indexed for the Doctrine CouchDB repository view
612
     * - isVersionField - Is this field containing the revision number of this document?
613
     *
614
     * @param array $mapping The mapping information.
615
     */
616
    public function mapField(array $mapping)
617
    {
618
        $mapping = $this->validateAndCompleteFieldMapping($mapping);
619
620
        if (!isset($mapping['type'])) {
621
            $mapping['type'] = "mixed";
622
        }
623
624
        if (isset($mapping['id']) && $mapping['id'] === true) {
625
            $mapping['type'] = 'string';
626
            $mapping['jsonName'] = '_id';
627
            $this->setIdentifier($mapping['fieldName']);
628
            if (isset($mapping['strategy'])) {
629
                $this->idGenerator = constant('Doctrine\ODM\CouchDB\Mapping\ClassMetadata::IDGENERATOR_' . strtoupper($mapping['strategy']));
630
                unset($mapping['strategy']);
631
            }
632
        } else if (isset($mapping['isVersionField'])) {
633
            $this->isVersioned = true;
634
            $this->versionField = $mapping['fieldName'];
635
        }
636
637
        $mapping = $this->checkAndStoreIndexMapping($mapping);
638
639
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
640
        $this->jsonNames[$mapping['jsonName']] = $mapping['fieldName'];
641
    }
642
643
    protected function validateAndCompleteFieldMapping($mapping)
644
    {
645
        if ( ! isset($mapping['fieldName']) || !$mapping['fieldName']) {
646
            throw new MappingException("Mapping a property requires to specify the name.");
647
        }
648
        if ( ! isset($mapping['jsonName'])) {
649
            $mapping['jsonName'] = $mapping['fieldName'];
650
        }
651
        if (isset($this->fieldMappings[$mapping['fieldName']]) || isset($this->associationsMappings[$mapping['fieldName']])) {
652
            throw MappingException::duplicateFieldMapping($this->name, $mapping['fieldName']);
653
        }
654
655
        return $mapping;
656
    }
657
658
    protected function validateAndCompleteReferenceMapping($mapping)
659
    {
660
        if (isset($mapping['targetDocument']) && $mapping['targetDocument'] && strpos($mapping['targetDocument'], '\\') === false && strlen($this->namespace)) {
661
            $mapping['targetDocument'] = $this->namespace . '\\' . $mapping['targetDocument'];
662
        }
663
        return $mapping;
664
    }
665
666
    protected function validateAndCompleteAssociationMapping($mapping)
667
    {
668
        $mapping = $this->validateAndCompleteFieldMapping($mapping);
669
670
        $mapping['sourceDocument'] = $this->name;
671
        $mapping = $this->validateAndCompleteReferenceMapping($mapping);
672
        return $mapping;
673
    }
674
675 View Code Duplication
    public function mapManyToOne($mapping)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
676
    {
677
        $mapping = $this->validateAndCompleteAssociationMapping($mapping);
678
679
        $mapping['isOwning'] = true;
680
        $mapping['type'] = self::MANY_TO_ONE;
681
682
        $mapping = $this->checkAndStoreIndexMapping($mapping);
683
684
        $this->storeAssociationMapping($mapping);
685
    }
686
687 View Code Duplication
    public function mapManyToMany($mapping)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
688
    {
689
        $mapping = $this->validateAndCompleteAssociationMapping($mapping);
690
691
        $mapping['isOwning'] = empty($mapping['mappedBy']);
692
        $mapping['type'] = self::MANY_TO_MANY;
693
694
        $this->storeAssociationMapping($mapping);
695
    }
696
697
    private function checkAndStoreIndexMapping($mapping)
698
    {
699
        if (isset($mapping['indexed']) && $mapping['indexed']) {
700
            $this->indexes[] = $mapping['fieldName'];
701
        }
702
        unset($mapping['indexed']);
703
704
        return $mapping;
705
    }
706
707
    private function storeAssociationMapping($mapping)
708
    {
709
        $this->associationsMappings[$mapping['fieldName']] = $mapping;
710
        $this->jsonNames[$mapping['jsonName']] = $mapping['fieldName'];
711
    }
712
713
    /**
714
     * A numerically indexed list of field names of this persistent class.
715
     *
716
     * This array includes identifier fields if present on this class.
717
     *
718
     * @return array
719
     */
720
    public function getFieldNames()
721
    {
722
        return array_keys($this->fieldMappings);
723
    }
724
725
    /**
726
     * Gets the mapping of a field.
727
     *
728
     * @param string $fieldName  The field name.
729
     * @return array  The field mapping.
730
     * @throws MappingException
731
     */
732
    public function getFieldMapping($fieldName)
733
    {
734
        if ( ! isset($this->fieldMappings[$fieldName])) {
735
            throw MappingException::mappingNotFound($this->name, $fieldName);
736
        }
737
        return $this->fieldMappings[$fieldName];
738
    }
739
740
    /**
741
     * Gets the type of a field.
742
     *
743
     * @param string $fieldName
744
     * @return Type
745
     */
746
    public function getTypeOfField($fieldName)
747
    {
748
        return isset($this->fieldMappings[$fieldName]) ?
749
                $this->fieldMappings[$fieldName]['type'] : null;
750
    }
751
752
    /**
753
     * Checks if the given field is a mapped association for this class.
754
     *
755
     * @param string $fieldName
756
     * @return boolean
757
     */
758
    public function hasAssociation($fieldName)
759
    {
760
        return isset($this->associationsMappings[$fieldName]);
761
    }
762
763
    public function isCollectionValuedAssociation($name)
764
    {
765
        // TODO: included @EmbedMany here also?
766
        return isset($this->associationsMappings[$name]) && ($this->associationsMappings[$name]['type'] & self::TO_MANY);
767
    }
768
769
    /**
770
     * Checks if the given field is a mapped single valued association for this class.
771
     *
772
     * @param string $fieldName
773
     * @return boolean
774
     */
775
    public function isSingleValuedAssociation($fieldName)
776
    {
777
        return isset($this->associationsMappings[$fieldName]) &&
778
                ($this->associationsMappings[$fieldName]['type'] & self::TO_ONE);
779
    }
780
781
    /**
782
     * A numerically indexed list of association names of this persistent class.
783
     *
784
     * This array includes identifier associations if present on this class.
785
     *
786
     * @return array
787
     */
788
    public function getAssociationNames()
789
    {
790
        return array_keys($this->associationsMappings);
791
    }
792
793
    /**
794
     * Returns the target class name of the given association.
795
     *
796
     * @param string $assocName
797
     * @return string
798
     * @throws \InvalidArgumentException
799
     */
800
    public function getAssociationTargetClass($assocName)
801
    {
802
        if (!isset($this->associationsMappings[$assocName])) {
803
            throw new \InvalidArgumentException("Association name expected, '" . $assocName ."' is not an association.");
804
        }
805
        return $this->associationsMappings[$assocName]['targetDocument'];
806
    }
807
808
    /**
809
     * {@inheritDoc}
810
     */
811
    public function getAssociationMappedByTargetField($assocName)
812
    {
813
        return $this->associationsMappings[$assocName]['mappedBy'];
814
    }
815
816
    /**
817
     * {@inheritDoc}
818
     */
819
    public function isAssociationInverseSide($assocName)
820
    {
821
        return isset($this->associationsMappings[$assocName]) && ! $this->associationsMappings[$assocName];
822
    }
823
824
    public function isInheritedField($field)
825
    {
826
        return isset($this->fieldMappings[$field]['declared']);
827
    }
828
829
    public function isInheritedAssociation($field)
830
    {
831
        return isset($this->associationsMappings[$field]['declared']);
832
    }
833
834
    public function setParentClasses($classes)
835
    {
836
        $this->parentClasses         = $classes;
837
        $this->inInheritanceHierachy = true;
838
        if (count($classes) > 0) {
839
            $this->rootDocumentName = array_pop($classes);
840
        }
841
    }
842
843
    public function markInheritanceRoot()
844
    {
845
        if ($this->parentClasses) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->parentClasses of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
846
            throw MappingException::invalidInheritanceRoot($this->name, $this->parentClasses);
847
        }
848
        $this->inInheritanceHierachy = true;
849
    }
850
851
    /**
852
     * Initializes a new ClassMetadata instance that will hold the object-relational mapping
853
     * metadata of the class with the given name.
854
     *
855
     * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService The reflection service.
856
     *
857
     * @return void
858
     */
859
    public function initializeReflection($reflService)
860
    {
861
        $this->reflClass = $reflService->getClass($this->name);
862
        $this->namespace = $reflService->getClassNamespace($this->name);
863
864
        if ($this->reflClass) {
865
            $this->name = $this->rootDocumentName = $this->reflClass->getName();
866
        }
867
    }
868
}
869
870