|
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
$myVarassignment in line 1 and the$higherassignment in line 2 are dead. The first because$myVaris never used and the second because$higheris always overwritten for every possible time line.