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; |
|
|
|
|
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(); |
|
|
|
|
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
|
|
|
|
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.
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.