Completed
Push — master ( 707688...6da863 )
by Mike
04:47 queued 02:08
created

ODM/CouchDB/Mapping/EmbeddedDocumentSerializer.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
$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
                $fieldMapping = $embeddedClass->fieldMappings[$fieldName];
89
90
                if ($value === null) {
91
                    continue;
92
                } else if (isset($fieldMapping['embedded'])) {
93
                    $data[$fieldMapping['jsonName']] = $this->serializeEmbeddedDocument($value, $fieldMapping);
94
                } else {
95
                    $data[$fieldMapping['jsonName']] = Type::getType($fieldMapping['type'])
96
                        ->convertToCouchDBValue($value);
97
                }
98
            }
99
        }
100
        return $data;
101
    }
102
103
    /**
104
     * Create a document for an embedded document field mapping from json data.
105
     *
106
     * @param array $data
107
     * @param object $embeddedFieldMapping
108
     * @return object
109
     * @throws \InvalidArgumentException
110
     */
111
    public function createEmbeddedDocument($data, $embeddedFieldMapping)
112
    {
113
        if ($data === null) {
114
            return null;
115
        } else if (!is_array($data)) {
116
            throw new \InvalidArgumentException("Cannot hydrate embedded if the data given is not an array");
117
        }
118
119
        if ('many' == $embeddedFieldMapping['embedded']) {
120
121
            $result = array();
122
            foreach ($data as $jsonName => $jsonValue) {
123
                if (!is_array($jsonValue)) {
124
                    throw new \InvalidArgumentException("Cannot hydrate many embedded if the data given is not an array");
125
                }
126
127
                $result[$jsonName] = $this->doCreateEmbeddedDocument($jsonValue, $embeddedFieldMapping);
128
            }
129
            ksort($result);
130
            return new ArrayCollection($result);
131
        } else {
132
            return $this->doCreateEmbeddedDocument($data, $embeddedFieldMapping);
133
        }
134
    }
135
136
    public function doCreateEmbeddedDocument($data, $embeddedFieldMapping)
137
    {
138
        if (!$this->metadataResolver->canMapDocument($data)) {
139
            if (!isset($embeddedFieldMapping['targetDocument'])) {
140
                throw new \InvalidArgumentException("Missing or missmatching metadata description in the EmbeddedDocument, cannot hydrate!");
141
            }
142
            $type = $embeddedFieldMapping['targetDocument'];
143
        } else {
144
            $type = $this->metadataResolver->getDocumentType($data);
145
        }
146
147
        $class = $this->metadataFactory->getMetadataFor($type);
148
        $instance = $class->newInstance();
149
150
        $documentState = array();
0 ignored issues
show
$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...
151
        foreach ($data as $jsonName => $jsonValue) {
152
            if ($this->metadataResolver->canResolveJsonField($jsonName)) {
153
                continue;
154
            }
155
            if (isset($class->jsonNames[$jsonName])) {
156
                $fieldName = $class->jsonNames[$jsonName];
157
                if (isset($class->fieldMappings[$fieldName])) {
158
                    if ($jsonValue === null) {
159
                        $fieldValue = null;
160
                    } else if (isset($class->fieldMappings[$fieldName]['embedded'])) {
161
                        $fieldValue = $this->createEmbeddedDocument($jsonValue, $class->fieldMappings[$fieldName]);
162
                    } else {
163
                        $fieldValue =
164
                            Type::getType($class->fieldMappings[$fieldName]['type'])
165
                            ->convertToPHPValue($jsonValue);
166
                    }
167
168
                    $class->setFieldValue($instance,
169
                                          $class->fieldMappings[$fieldName]['fieldName'],
170
                                          $fieldValue);
171
172
173
                }
174
            } else {
0 ignored issues
show
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...
175
                //$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...
176
            }
177
        }
178
        return $instance;
179
    }
180
181
182
    /**
183
     * Compares the two representation of an embedded document.
184
     *
185
     * If the original misses doctrine_metadata, but the values are the same, we assume there is no change
186
     * If the original has doctrine_metadata, and the new value has different class, that's a change,
187
     * even if the values are the same.
188
     *
189
     * @param array $value
190
     * @param object $originalData
191
     * @param array $valueFieldMapping Mapping of the field that contains the embedded document in the embedder document.
192
     * @return boolean
193
     */
194
    public function isChanged($value, $originalData, $valueFieldMapping)
195
    {
196
        // EmbedMany case
197
        if ('many' == $valueFieldMapping['embedded'] && (is_array($value) || $value instanceof \Doctrine\Common\Collections\ArrayCollection)) {
198
            if (count($originalData) != count($value)) {
199
                return true;
200
            }
201
            foreach ($value as $key => $valueElement) {
202
                if (!isset($originalData[$key])
203
                    || $this->isChanged($valueElement, $originalData[$key], $valueFieldMapping)) {
204
                    return true;
205
                }
206
            }
207
            return false;
208
        }
209
210
        // EmbedOne case, or one instance of and EmbedMany
211
        if ($this->metadataResolver->canMapDocument($originalData)
212
            && get_class($value) !== $this->metadataResolver->getDocumentType($originalData)) {
213
            return true;
214
        }
215
216
        $class = $this->metadataFactory->getMetadataFor(get_class($value));
217
        foreach ($class->reflFields as $fieldName => $fieldValue) {
218
            $fieldMapping = $class->fieldMappings[$fieldName];
219
            $originalDataValue = isset($originalData[$fieldMapping['jsonName']])
220
                ? $originalData[$fieldMapping['jsonName']]
221
                : null;
222
223
            $currentValue = $class->getFieldValue($value, $fieldMapping['fieldName']);
224
225
            if ($originalDataValue === null && $currentValue === null) {
226
                continue;
227
            } else if ($originalDataValue === null || $currentValue === null) {
228
                return true;
229
            }
230
231
            if (!isset($fieldMapping['embedded'])) {
232
                // simple property comparison
233
                // TODO this conversion could be avoided if we store the php value in the original data
234
                //      as with the simple property mapping in UOW.
235
                $originalValue = Type::getType($fieldMapping['type'])
236
                    ->convertToPHPValue($originalDataValue);
237
                if ($originalValue != $currentValue) {
238
                    return true;
239
                }
240
            } else {
241
242
                if ('many' == $fieldMapping['embedded']) {
243
                    if (count($originalDataValue) != count($currentValue)) {
244
                        return true;
245
                    }
246
                    foreach ($currentValue as $currentKey => $currentElem) {
247
                        if (!isset($originalDataValue[$currentKey])) {
248
                            return true;
249
                        }
250
                        if ($this->isChanged($currentElem, $originalDataValue[$currentKey], $fieldMapping)) {
251
                            return true;
252
                        }
253
                    }
254
                } else { // embedOne
255
                    if ($this->isChanged($currentValue, $originalDataValue, $fieldMapping)) {
256
                        return true;
257
                    }
258
                }
259
260
            }
261
        }
262
        return false;
263
    }
264
}
265