Completed
Pull Request — master (#137)
by
unknown
02:42
created

serializeEmbeddedDocument()   C

Complexity

Conditions 22
Paths 6

Size

Total Lines 79
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 79
rs 5.0472
cc 22
eloc 51
nc 6
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\CouchDB\Mapping;
21
22
use Doctrine\Common\Collections\ArrayCollection;
23
use Doctrine\ODM\CouchDB\Mapping\ClassMetadata;
24
use Doctrine\ODM\CouchDB\Types\Type;
25
use Doctrine\Common\Util\ClassUtils;
26
27
/**
28
 * Helper class serializing/unserializing embedded documents.
29
 *
30
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
31
 * @link        www.doctrine-project.com
32
 * @since       1.0
33
 * @author      Bartfai Tamas <[email protected]>
34
 */
35
class EmbeddedDocumentSerializer
36
{
37
    private $metadataFactory;
38
39
    private $metadataResolver;
40
41
    public function __construct($metadataFactory, $metadataResolver)
42
    {
43
        $this->metadataFactory = $metadataFactory;
44
        $this->metadataResolver = $metadataResolver;
45
    }
46
47
    /**
48
     * Serializes an embedded document value into array given the mapping
49
     * metadata for the class.
50
     *
51
     * @param object $embeddedValue
52
     * @param array $embeddedFieldMapping
53
     * @param $embedMany
54
     * @return array
55
     * @throws \InvalidArgumentException
56
     */
57
    public function serializeEmbeddedDocument($embeddedValue, $embeddedFieldMapping, $embedMany = false)
58
    {
59
        if ($embeddedValue === null) {
60
            return null;
61
        }
62
63
        if (!$embedMany && 'many' == $embeddedFieldMapping['embedded'] && (is_array($embeddedValue) || $embeddedValue instanceof \Traversable)) {
64
            $data = array();
65
            foreach ($embeddedValue as $key => $val) {
66
                $data[$key] = $this->serializeEmbeddedDocument($val, $embeddedFieldMapping, true);
67
            }
68
        } else {
69
            $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...
70
            if (isset($embeddedFieldMapping['targetDocument'])) {
71
                $embeddedClass = $this->metadataFactory->getMetadataFor(ClassUtils::getClass($embeddedValue));
72
73
                if ($embeddedClass->name !== $embeddedFieldMapping['targetDocument'] &&
74
                    !is_subclass_of($embeddedClass->name, $embeddedFieldMapping['targetDocument']) ) {
75
76
                    throw new \InvalidArgumentException(
77
                        'Mismatching metadata description in the EmbeddedDocument, expected class ' .
78
                        $embeddedFieldMapping['targetDocument'] . ' but got ' . get_class($embeddedValue)
79
                    );
80
                }
81
            } else {
82
                $embeddedClass = $this->metadataFactory->getMetadataFor(get_class($embeddedValue));
83
            }
84
85
            $data = $this->metadataResolver->createDefaultDocumentStruct($embeddedClass);
86
            foreach($embeddedClass->reflFields AS $fieldName => $reflProperty) {
87
                $value = $reflProperty->getValue($embeddedValue);
88
                
89
                if (isset($fieldName, $embeddedClass->fieldMappings[$fieldName])) {
90
                    $fieldMapping = $embeddedClass->fieldMappings[$fieldName];
91
                } elseif (isset($embeddedClass->associationsMappings, $embeddedClass->associationsMappings[$fieldName])) {
92
                    /** @see UnitOfWork::flush() */
93
                    if ($embeddedClass->associationsMappings[$fieldName]['type'] & ClassMetadata::TO_ONE) {
94
                        $fieldValue = null;
95
                        if (\is_object($value)) {
96
                            $fieldValueMetadata = $this->metadataFactory->getMetadataFor(get_class($value));
97
                            $fieldValue = $fieldValueMetadata->getIdentifierValue($value);
98
                        }
99
                        $data['doctrine_metadata']['associations'][] = $fieldName;
100
                        $data[$fieldName] = $fieldValue;
101
                    } else if ($embeddedClass->associationsMappings[$fieldName]['type'] & ClassMetadata::TO_MANY) {
102
103
                        if ($embeddedClass->associationsMappings[$fieldName]['isOwning']) {
104
                            // TODO: Optimize when not initialized yet! In ManyToMany case we can keep track of ALL ids
105
                            $ids = array();
106
                            if (is_array($value) || $value instanceof \Doctrine\Common\Collections\Collection) {
107
                                foreach ($value AS $key => $relatedObject) {
108
                                    $fieldValueMetadata = $this->metadataFactory->getMetadataFor(get_class($relatedObject));
109
                                    $ids[$key] = $fieldValueMetadata->getIdentifierValue($relatedObject);
110
                                }
111
                            }
112
113
                            $data['doctrine_metadata']['associations'][] = $ids;
114
                            $data[$fieldName] = $ids;
115
                        }
116
                    }
117
118
                    continue;
119
                } else {
120
                    $fieldMapping = [];
121
                }
122
123
                if ($value === null) {
124
                    continue;
125
                } else if (isset($fieldMapping['embedded'])) {
126
                    $data[$fieldMapping['jsonName']] = $this->serializeEmbeddedDocument($value, $fieldMapping);
127
                } else {
128
                    $data[$fieldMapping['jsonName']] = Type::getType($fieldMapping['type'])
129
                        ->convertToCouchDBValue($value);
130
                }
131
            }
132
        }
133
        
134
        return $data;
135
    }
136
137
    /**
138
     * Create a document for an embedded document field mapping from json data.
139
     *
140
     * @param array $data
141
     * @param object $embeddedFieldMapping
142
     * @return object
143
     * @throws \InvalidArgumentException
144
     */
145
    public function createEmbeddedDocument($data, $embeddedFieldMapping)
146
    {
147
        if ($data === null) {
148
            return null;
149
        } else if (!is_array($data)) {
150
            throw new \InvalidArgumentException("Cannot hydrate embedded if the data given is not an array");
151
        }
152
153
        if ('many' == $embeddedFieldMapping['embedded']) {
154
155
            $result = array();
156
            foreach ($data as $jsonName => $jsonValue) {
157
                if (!is_array($jsonValue)) {
158
                    throw new \InvalidArgumentException("Cannot hydrate many embedded if the data given is not an array");
159
                }
160
161
                $result[$jsonName] = $this->doCreateEmbeddedDocument($jsonValue, $embeddedFieldMapping);
162
            }
163
            ksort($result);
164
            return new ArrayCollection($result);
165
        } else {
166
            return $this->doCreateEmbeddedDocument($data, $embeddedFieldMapping);
167
        }
168
    }
169
170
    public function doCreateEmbeddedDocument($data, $embeddedFieldMapping)
171
    {
172
        if (!$this->metadataResolver->canMapDocument($data)) {
173
            if (!isset($embeddedFieldMapping['targetDocument'])) {
174
                throw new \InvalidArgumentException("Missing or missmatching metadata description in the EmbeddedDocument, cannot hydrate!");
175
            }
176
            $type = $embeddedFieldMapping['targetDocument'];
177
        } else {
178
            $type = $this->metadataResolver->getDocumentType($data);
179
        }
180
181
        $class = $this->metadataFactory->getMetadataFor($type);
182
        $instance = $class->newInstance();
183
184
        $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...
185
        foreach ($data as $jsonName => $jsonValue) {
186
            if ($this->metadataResolver->canResolveJsonField($jsonName)) {
187
                continue;
188
            }
189
            if (isset($class->jsonNames[$jsonName])) {
190
                $fieldName = $class->jsonNames[$jsonName];
191
                if (isset($class->fieldMappings[$fieldName])) {
192
                    if ($jsonValue === null) {
193
                        $fieldValue = null;
194
                    } else if (isset($class->fieldMappings[$fieldName]['embedded'])) {
195
                        $fieldValue = $this->createEmbeddedDocument($jsonValue, $class->fieldMappings[$fieldName]);
196
                    } else {
197
                        $fieldValue =
198
                            Type::getType($class->fieldMappings[$fieldName]['type'])
199
                            ->convertToPHPValue($jsonValue);
200
                    }
201
202
                    $class->setFieldValue($instance,
203
                                          $class->fieldMappings[$fieldName]['fieldName'],
204
                                          $fieldValue);
205
206
207
                }
208
            } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
209
                //$nonMappedData[$jsonName] = $jsonValue;
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
210
            }
211
        }
212
        return $instance;
213
    }
214
215
216
    /**
217
     * Compares the two representation of an embedded document.
218
     *
219
     * If the original misses doctrine_metadata, but the values are the same, we assume there is no change
220
     * If the original has doctrine_metadata, and the new value has different class, that's a change,
221
     * even if the values are the same.
222
     *
223
     * @param array $value
224
     * @param object $originalData
225
     * @param array $valueFieldMapping Mapping of the field that contains the embedded document in the embedder document.
226
     * @return boolean
227
     */
228
    public function isChanged($value, $originalData, $valueFieldMapping)
229
    {
230
        // EmbedMany case
231
        if ('many' == $valueFieldMapping['embedded'] && (is_array($value) || $value instanceof \Doctrine\Common\Collections\ArrayCollection)) {
232
            if (count($originalData) != count($value)) {
233
                return true;
234
            }
235
            foreach ($value as $key => $valueElement) {
236
                if (!isset($originalData[$key])
237
                    || $this->isChanged($valueElement, $originalData[$key], $valueFieldMapping)) {
238
                    return true;
239
                }
240
            }
241
            return false;
242
        }
243
244
        // EmbedOne case, or one instance of and EmbedMany
245
        if ($this->metadataResolver->canMapDocument($originalData)
246
            && get_class($value) !== $this->metadataResolver->getDocumentType($originalData)) {
247
            return true;
248
        }
249
250
        $class = $this->metadataFactory->getMetadataFor(get_class($value));
251
        foreach ($class->reflFields as $fieldName => $fieldValue) {
252
            $fieldMapping = $class->fieldMappings[$fieldName];
253
            $originalDataValue = isset($originalData[$fieldMapping['jsonName']])
254
                ? $originalData[$fieldMapping['jsonName']]
255
                : null;
256
257
            $currentValue = $class->getFieldValue($value, $fieldMapping['fieldName']);
258
259
            if ($originalDataValue === null && $currentValue === null) {
260
                continue;
261
            } else if ($originalDataValue === null || $currentValue === null) {
262
                return true;
263
            }
264
265
            if (!isset($fieldMapping['embedded'])) {
266
                // simple property comparison
267
                // TODO this conversion could be avoided if we store the php value in the original data
268
                //      as with the simple property mapping in UOW.
269
                $originalValue = Type::getType($fieldMapping['type'])
270
                    ->convertToPHPValue($originalDataValue);
271
                if ($originalValue != $currentValue) {
272
                    return true;
273
                }
274
            } else {
275
276
                if ('many' == $fieldMapping['embedded']) {
277
                    if (count($originalDataValue) != count($currentValue)) {
278
                        return true;
279
                    }
280
                    foreach ($currentValue as $currentKey => $currentElem) {
281
                        if (!isset($originalDataValue[$currentKey])) {
282
                            return true;
283
                        }
284
                        if ($this->isChanged($currentElem, $originalDataValue[$currentKey], $fieldMapping)) {
285
                            return true;
286
                        }
287
                    }
288
                } else { // embedOne
289
                    if ($this->isChanged($currentValue, $originalDataValue, $fieldMapping)) {
290
                        return true;
291
                    }
292
                }
293
294
            }
295
        }
296
        return false;
297
    }
298
}
299