EmbeddedDocumentSerializer   C
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 230
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 53
lcom 1
cbo 3
dl 0
loc 230
rs 6.96
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
C serializeEmbeddedDocument() 0 45 13
B createEmbeddedDocument() 0 24 6
B doCreateEmbeddedDocument() 0 44 9
D isChanged() 0 70 24

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace Doctrine\ODM\CouchDB\Mapping;
4
5
use Doctrine\Common\Collections\ArrayCollection;
6
use Doctrine\ODM\CouchDB\Mapping\ClassMetadata;
7
use Doctrine\ODM\CouchDB\Types\Type;
8
use Doctrine\Common\Util\ClassUtils;
9
10
/**
11
 * Helper class serializing/unserializing embedded documents.
12
 *
13
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
14
 * @link        www.doctrine-project.com
15
 * @since       1.0
16
 * @author      Bartfai Tamas <[email protected]>
17
 */
18
class EmbeddedDocumentSerializer
19
{
20
    private $metadataFactory;
21
22
    private $metadataResolver;
23
24
    public function __construct($metadataFactory, $metadataResolver)
25
    {
26
        $this->metadataFactory = $metadataFactory;
27
        $this->metadataResolver = $metadataResolver;
28
    }
29
30
    /**
31
     * Serializes an embedded document value into array given the mapping
32
     * metadata for the class.
33
     *
34
     * @param object $embeddedValue
35
     * @param array $embeddedFieldMapping
36
     * @param $embedMany
37
     * @return array
38
     * @throws \InvalidArgumentException
39
     */
40
    public function serializeEmbeddedDocument($embeddedValue, $embeddedFieldMapping, $embedMany = false)
41
    {
42
        if ($embeddedValue === null) {
43
            return null;
44
        }
45
46
        if (!$embedMany && 'many' == $embeddedFieldMapping['embedded'] && (is_array($embeddedValue) || $embeddedValue instanceof \Traversable)) {
47
            $data = array();
48
            foreach ($embeddedValue as $key => $val) {
49
                $data[$key] = $this->serializeEmbeddedDocument($val, $embeddedFieldMapping, true);
50
            }
51
        } else {
52
            $embeddedClass = null;
0 ignored issues
show
Unused Code introduced by
$embeddedClass is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
53
            if (isset($embeddedFieldMapping['targetDocument'])) {
54
                $embeddedClass = $this->metadataFactory->getMetadataFor(ClassUtils::getClass($embeddedValue));
55
56
                if ($embeddedClass->name !== $embeddedFieldMapping['targetDocument'] &&
57
                    !is_subclass_of($embeddedClass->name, $embeddedFieldMapping['targetDocument']) ) {
58
59
                    throw new \InvalidArgumentException(
60
                        'Mismatching metadata description in the EmbeddedDocument, expected class ' .
61
                        $embeddedFieldMapping['targetDocument'] . ' but got ' . get_class($embeddedValue)
62
                    );
63
                }
64
            } else {
65
                $embeddedClass = $this->metadataFactory->getMetadataFor(get_class($embeddedValue));
66
            }
67
68
            $data = $this->metadataResolver->createDefaultDocumentStruct($embeddedClass);
69
            foreach($embeddedClass->reflFields AS $fieldName => $reflProperty) {
70
                $value = $reflProperty->getValue($embeddedValue);
71
                $fieldMapping = $embeddedClass->fieldMappings[$fieldName];
72
73
                if ($value === null) {
74
                    continue;
75
                } else if (isset($fieldMapping['embedded'])) {
76
                    $data[$fieldMapping['jsonName']] = $this->serializeEmbeddedDocument($value, $fieldMapping);
77
                } else {
78
                    $data[$fieldMapping['jsonName']] = Type::getType($fieldMapping['type'])
79
                        ->convertToCouchDBValue($value);
80
                }
81
            }
82
        }
83
        return $data;
84
    }
85
86
    /**
87
     * Create a document for an embedded document field mapping from json data.
88
     *
89
     * @param array $data
90
     * @param object $embeddedFieldMapping
91
     * @return object
92
     * @throws \InvalidArgumentException
93
     */
94
    public function createEmbeddedDocument($data, $embeddedFieldMapping)
95
    {
96
        if ($data === null) {
97
            return null;
98
        } else if (!is_array($data)) {
99
            throw new \InvalidArgumentException("Cannot hydrate embedded if the data given is not an array");
100
        }
101
102
        if ('many' == $embeddedFieldMapping['embedded']) {
103
104
            $result = array();
105
            foreach ($data as $jsonName => $jsonValue) {
106
                if (!is_array($jsonValue)) {
107
                    throw new \InvalidArgumentException("Cannot hydrate many embedded if the data given is not an array");
108
                }
109
110
                $result[$jsonName] = $this->doCreateEmbeddedDocument($jsonValue, $embeddedFieldMapping);
111
            }
112
            ksort($result);
113
            return new ArrayCollection($result);
114
        } else {
115
            return $this->doCreateEmbeddedDocument($data, $embeddedFieldMapping);
116
        }
117
    }
118
119
    public function doCreateEmbeddedDocument($data, $embeddedFieldMapping)
120
    {
121
        if (!$this->metadataResolver->canMapDocument($data)) {
122
            if (!isset($embeddedFieldMapping['targetDocument'])) {
123
                throw new \InvalidArgumentException("Missing or missmatching metadata description in the EmbeddedDocument, cannot hydrate!");
124
            }
125
            $type = $embeddedFieldMapping['targetDocument'];
126
        } else {
127
            $type = $this->metadataResolver->getDocumentType($data);
128
        }
129
130
        $class = $this->metadataFactory->getMetadataFor($type);
131
        $instance = $class->newInstance();
132
133
        $documentState = array();
0 ignored issues
show
Unused Code introduced by
$documentState is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
134
        foreach ($data as $jsonName => $jsonValue) {
135
            if ($this->metadataResolver->canResolveJsonField($jsonName)) {
136
                continue;
137
            }
138
            if (isset($class->jsonNames[$jsonName])) {
139
                $fieldName = $class->jsonNames[$jsonName];
140
                if (isset($class->fieldMappings[$fieldName])) {
141
                    if ($jsonValue === null) {
142
                        $fieldValue = null;
143
                    } else if (isset($class->fieldMappings[$fieldName]['embedded'])) {
144
                        $fieldValue = $this->createEmbeddedDocument($jsonValue, $class->fieldMappings[$fieldName]);
145
                    } else {
146
                        $fieldValue =
147
                            Type::getType($class->fieldMappings[$fieldName]['type'])
148
                            ->convertToPHPValue($jsonValue);
149
                    }
150
151
                    $class->setFieldValue($instance,
152
                                          $class->fieldMappings[$fieldName]['fieldName'],
153
                                          $fieldValue);
154
155
156
                }
157
            } else {
158
                //$nonMappedData[$jsonName] = $jsonValue;
159
            }
160
        }
161
        return $instance;
162
    }
163
164
165
    /**
166
     * Compares the two representation of an embedded document.
167
     *
168
     * If the original misses doctrine_metadata, but the values are the same, we assume there is no change
169
     * If the original has doctrine_metadata, and the new value has different class, that's a change,
170
     * even if the values are the same.
171
     *
172
     * @param array $value
173
     * @param object $originalData
174
     * @param array $valueFieldMapping Mapping of the field that contains the embedded document in the embedder document.
175
     * @return boolean
176
     */
177
    public function isChanged($value, $originalData, $valueFieldMapping)
178
    {
179
        // EmbedMany case
180
        if ('many' == $valueFieldMapping['embedded'] && (is_array($value) || $value instanceof \Doctrine\Common\Collections\ArrayCollection)) {
181
            if (count($originalData) != count($value)) {
182
                return true;
183
            }
184
            foreach ($value as $key => $valueElement) {
185
                if (!isset($originalData[$key])
186
                    || $this->isChanged($valueElement, $originalData[$key], $valueFieldMapping)) {
187
                    return true;
188
                }
189
            }
190
            return false;
191
        }
192
193
        // EmbedOne case, or one instance of and EmbedMany
194
        if ($this->metadataResolver->canMapDocument($originalData)
195
            && get_class($value) !== $this->metadataResolver->getDocumentType($originalData)) {
196
            return true;
197
        }
198
199
        $class = $this->metadataFactory->getMetadataFor(get_class($value));
200
        foreach ($class->reflFields as $fieldName => $fieldValue) {
201
            $fieldMapping = $class->fieldMappings[$fieldName];
202
            $originalDataValue = isset($originalData[$fieldMapping['jsonName']])
203
                ? $originalData[$fieldMapping['jsonName']]
204
                : null;
205
206
            $currentValue = $class->getFieldValue($value, $fieldMapping['fieldName']);
207
208
            if ($originalDataValue === null && $currentValue === null) {
209
                continue;
210
            } else if ($originalDataValue === null || $currentValue === null) {
211
                return true;
212
            }
213
214
            if (!isset($fieldMapping['embedded'])) {
215
                // simple property comparison
216
                // TODO this conversion could be avoided if we store the php value in the original data
217
                //      as with the simple property mapping in UOW.
218
                $originalValue = Type::getType($fieldMapping['type'])
219
                    ->convertToPHPValue($originalDataValue);
220
                if ($originalValue != $currentValue) {
221
                    return true;
222
                }
223
            } else {
224
225
                if ('many' == $fieldMapping['embedded']) {
226
                    if (count($originalDataValue) != count($currentValue)) {
227
                        return true;
228
                    }
229
                    foreach ($currentValue as $currentKey => $currentElem) {
230
                        if (!isset($originalDataValue[$currentKey])) {
231
                            return true;
232
                        }
233
                        if ($this->isChanged($currentElem, $originalDataValue[$currentKey], $fieldMapping)) {
234
                            return true;
235
                        }
236
                    }
237
                } else { // embedOne
238
                    if ($this->isChanged($currentValue, $originalDataValue, $fieldMapping)) {
239
                        return true;
240
                    }
241
                }
242
243
            }
244
        }
245
        return false;
246
    }
247
}
248