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; |
|
|
|
|
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(); |
|
|
|
|
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 { |
|
|
|
|
209
|
|
|
//$nonMappedData[$jsonName] = $jsonValue; |
|
|
|
|
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
|
|
|
|
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.