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
|
|
|
namespace Doctrine\ODM\MongoDB\Persisters; |
20
|
|
|
|
21
|
|
|
use Doctrine\ODM\MongoDB\DocumentManager; |
22
|
|
|
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; |
23
|
|
|
use Doctrine\ODM\MongoDB\PersistentCollection; |
24
|
|
|
use Doctrine\ODM\MongoDB\Types\Type; |
25
|
|
|
use Doctrine\ODM\MongoDB\UnitOfWork; |
26
|
|
|
use Doctrine\ODM\MongoDB\Utility\CollectionHelper; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* PersistenceBuilder builds the queries used by the persisters to update and insert |
30
|
|
|
* documents when a DocumentManager is flushed. It uses the changeset information in the |
31
|
|
|
* UnitOfWork to build queries using atomic operators like $set, $unset, etc. |
32
|
|
|
* |
33
|
|
|
* @since 1.0 |
34
|
|
|
* @author Jonathan H. Wage <[email protected]> |
35
|
|
|
*/ |
36
|
|
|
class PersistenceBuilder |
37
|
|
|
{ |
38
|
|
|
/** |
39
|
|
|
* The DocumentManager instance. |
40
|
|
|
* |
41
|
|
|
* @var DocumentManager |
42
|
|
|
*/ |
43
|
|
|
private $dm; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* The UnitOfWork instance. |
47
|
|
|
* |
48
|
|
|
* @var UnitOfWork |
49
|
|
|
*/ |
50
|
|
|
private $uow; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Initializes a new PersistenceBuilder instance. |
54
|
|
|
* |
55
|
|
|
* @param DocumentManager $dm |
56
|
|
|
* @param UnitOfWork $uow |
57
|
|
|
*/ |
58
|
683 |
|
public function __construct(DocumentManager $dm, UnitOfWork $uow) |
59
|
|
|
{ |
60
|
683 |
|
$this->dm = $dm; |
61
|
683 |
|
$this->uow = $uow; |
62
|
683 |
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Prepares the array that is ready to be inserted to mongodb for a given object document. |
66
|
|
|
* |
67
|
|
|
* @param object $document |
68
|
|
|
* @return array $insertData |
69
|
|
|
*/ |
70
|
486 |
|
public function prepareInsertData($document) |
71
|
|
|
{ |
72
|
486 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
73
|
486 |
|
$changeset = $this->uow->getDocumentChangeSet($document); |
74
|
|
|
|
75
|
486 |
|
$insertData = array(); |
76
|
486 |
|
foreach ($class->fieldMappings as $mapping) { |
77
|
|
|
|
78
|
486 |
|
$new = isset($changeset[$mapping['fieldName']][1]) ? $changeset[$mapping['fieldName']][1] : null; |
79
|
|
|
|
80
|
486 |
|
if ($new === null && $mapping['nullable']) { |
81
|
145 |
|
$insertData[$mapping['name']] = null; |
82
|
145 |
|
} |
83
|
|
|
|
84
|
|
|
/* Nothing more to do for null values, since we're either storing |
85
|
|
|
* them (if nullable was true) or not. |
86
|
|
|
*/ |
87
|
486 |
|
if ($new === null) { |
88
|
341 |
|
continue; |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
// @Field, @String, @Date, etc. |
92
|
486 |
|
if ( ! isset($mapping['association'])) { |
93
|
486 |
|
$insertData[$mapping['name']] = Type::getType($mapping['type'])->convertToDatabaseValue($new); |
94
|
|
|
|
95
|
|
|
// @ReferenceOne |
96
|
486 |
|
} elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::REFERENCE_ONE) { |
97
|
78 |
|
$insertData[$mapping['name']] = $this->prepareReferencedDocumentValue($mapping, $new); |
98
|
|
|
|
99
|
|
|
// @EmbedOne |
100
|
397 |
|
} elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::EMBED_ONE) { |
101
|
65 |
|
$insertData[$mapping['name']] = $this->prepareEmbeddedDocumentValue($mapping, $new); |
102
|
|
|
|
103
|
|
|
// @ReferenceMany, @EmbedMany |
104
|
|
|
// We're excluding collections using addToSet since there is a risk |
105
|
|
|
// of duplicated entries stored in the collection |
106
|
373 |
|
} elseif ($mapping['type'] === ClassMetadata::MANY && ! $mapping['isInverseSide'] |
107
|
351 |
|
&& $mapping['strategy'] !== 'addToSet' && ! $new->isEmpty()) { |
108
|
195 |
|
$insertData[$mapping['name']] = $this->prepareAssociatedCollectionValue($new, true); |
109
|
195 |
|
} |
110
|
486 |
|
} |
111
|
|
|
|
112
|
|
|
// add discriminator if the class has one |
113
|
485 |
View Code Duplication |
if (isset($class->discriminatorField)) { |
|
|
|
|
114
|
32 |
|
$insertData[$class->discriminatorField] = isset($class->discriminatorValue) |
115
|
32 |
|
? $class->discriminatorValue |
116
|
32 |
|
: $class->name; |
117
|
32 |
|
} |
118
|
|
|
|
119
|
485 |
|
return $insertData; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Prepares the update query to update a given document object in mongodb. |
124
|
|
|
* |
125
|
|
|
* @param object $document |
126
|
|
|
* @return array $updateData |
127
|
|
|
*/ |
128
|
213 |
|
public function prepareUpdateData($document) |
129
|
|
|
{ |
130
|
213 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
131
|
213 |
|
$changeset = $this->uow->getDocumentChangeSet($document); |
132
|
|
|
|
133
|
213 |
|
$updateData = array(); |
134
|
213 |
|
foreach ($changeset as $fieldName => $change) { |
135
|
172 |
|
$mapping = $class->fieldMappings[$fieldName]; |
136
|
|
|
|
137
|
|
|
// skip non embedded document identifiers |
138
|
172 |
|
if ( ! $class->isEmbeddedDocument && ! empty($mapping['id'])) { |
139
|
1 |
|
continue; |
140
|
|
|
} |
141
|
|
|
|
142
|
172 |
|
list($old, $new) = $change; |
143
|
|
|
|
144
|
|
|
// @Inc |
145
|
172 |
|
if ($mapping['type'] === 'increment') { |
146
|
4 |
|
if ($new === null) { |
147
|
1 |
|
if ($mapping['nullable'] === true) { |
148
|
|
|
$updateData['$set'][$mapping['name']] = null; |
149
|
|
|
} else { |
150
|
1 |
|
$updateData['$unset'][$mapping['name']] = true; |
151
|
|
|
} |
152
|
4 |
|
} elseif ($new >= $old) { |
153
|
4 |
|
$updateData['$inc'][$mapping['name']] = $new - $old; |
154
|
4 |
|
} else { |
155
|
1 |
|
$updateData['$inc'][$mapping['name']] = ($old - $new) * -1; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
// @Field, @String, @Date, etc. |
159
|
172 |
|
} elseif ( ! isset($mapping['association'])) { |
160
|
112 |
|
if (isset($new) || $mapping['nullable'] === true) { |
161
|
112 |
|
$updateData['$set'][$mapping['name']] = (is_null($new) ? null : Type::getType($mapping['type'])->convertToDatabaseValue($new)); |
162
|
112 |
|
} else { |
163
|
|
|
$updateData['$unset'][$mapping['name']] = true; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
// @EmbedOne |
167
|
169 |
|
} elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::EMBED_ONE) { |
168
|
|
|
// If we have a new embedded document then lets set the whole thing |
169
|
24 |
|
if ($new && $this->uow->isScheduledForInsert($new)) { |
170
|
8 |
|
$updateData['$set'][$mapping['name']] = $this->prepareEmbeddedDocumentValue($mapping, $new); |
171
|
|
|
|
172
|
|
|
// If we don't have a new value then lets unset the embedded document |
173
|
24 |
|
} elseif ( ! $new) { |
174
|
3 |
|
$updateData['$unset'][$mapping['name']] = true; |
175
|
|
|
|
176
|
|
|
// Update existing embedded document |
177
|
3 |
View Code Duplication |
} else { |
|
|
|
|
178
|
16 |
|
$update = $this->prepareUpdateData($new); |
179
|
16 |
|
foreach ($update as $cmd => $values) { |
180
|
14 |
|
foreach ($values as $key => $value) { |
181
|
14 |
|
$updateData[$cmd][$mapping['name'] . '.' . $key] = $value; |
182
|
14 |
|
} |
183
|
16 |
|
} |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
// @ReferenceMany, @EmbedMany |
187
|
94 |
|
} elseif (isset($mapping['association']) && $mapping['type'] === 'many' && $new) { |
188
|
64 |
|
if (CollectionHelper::isAtomic($mapping['strategy']) && $this->uow->isCollectionScheduledForUpdate($new)) { |
189
|
12 |
|
$updateData['$set'][$mapping['name']] = $this->prepareAssociatedCollectionValue($new, true); |
190
|
64 |
View Code Duplication |
} elseif (CollectionHelper::isAtomic($mapping['strategy']) && $this->uow->isCollectionScheduledForDeletion($new)) { |
|
|
|
|
191
|
|
|
$updateData['$unset'][$mapping['name']] = true; |
192
|
|
|
$this->uow->unscheduleCollectionDeletion($new); |
193
|
52 |
|
} elseif (CollectionHelper::isAtomic($mapping['strategy']) && $this->uow->isCollectionScheduledForDeletion($old)) { |
194
|
2 |
|
$updateData['$unset'][$mapping['name']] = true; |
195
|
2 |
|
$this->uow->unscheduleCollectionDeletion($old); |
196
|
52 |
|
} elseif ($mapping['association'] === ClassMetadata::EMBED_MANY) { |
197
|
41 |
|
foreach ($new as $key => $embeddedDoc) { |
198
|
39 |
|
if ( ! $this->uow->isScheduledForInsert($embeddedDoc)) { |
199
|
28 |
|
$update = $this->prepareUpdateData($embeddedDoc); |
200
|
28 |
|
foreach ($update as $cmd => $values) { |
201
|
14 |
|
foreach ($values as $name => $value) { |
202
|
14 |
|
$updateData[$cmd][$mapping['name'] . '.' . $key . '.' . $name] = $value; |
203
|
14 |
|
} |
204
|
28 |
|
} |
205
|
28 |
|
} |
206
|
41 |
|
} |
207
|
41 |
|
} |
208
|
|
|
|
209
|
|
|
// @ReferenceOne |
210
|
76 |
|
} elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::REFERENCE_ONE) { |
211
|
11 |
View Code Duplication |
if (isset($new) || $mapping['nullable'] === true) { |
|
|
|
|
212
|
11 |
|
$updateData['$set'][$mapping['name']] = (is_null($new) ? null : $this->prepareReferencedDocumentValue($mapping, $new)); |
213
|
11 |
|
} else { |
214
|
2 |
|
$updateData['$unset'][$mapping['name']] = true; |
215
|
|
|
} |
216
|
11 |
|
} |
217
|
213 |
|
} |
218
|
|
|
// collections that aren't dirty but could be subject to update are |
219
|
|
|
// excluded from change set, let's go through them now |
220
|
213 |
|
foreach ($this->uow->getScheduledCollections($document) as $coll) { |
221
|
100 |
|
$mapping = $coll->getMapping(); |
222
|
100 |
|
if (CollectionHelper::isAtomic($mapping['strategy']) && $this->uow->isCollectionScheduledForUpdate($coll)) { |
223
|
10 |
|
$updateData['$set'][$mapping['name']] = $this->prepareAssociatedCollectionValue($coll, true); |
224
|
100 |
View Code Duplication |
} elseif (CollectionHelper::isAtomic($mapping['strategy']) && $this->uow->isCollectionScheduledForDeletion($coll)) { |
|
|
|
|
225
|
2 |
|
$updateData['$unset'][$mapping['name']] = true; |
226
|
2 |
|
$this->uow->unscheduleCollectionDeletion($coll); |
227
|
2 |
|
} |
228
|
|
|
// @ReferenceMany is handled by CollectionPersister |
229
|
213 |
|
} |
230
|
213 |
|
return $updateData; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
/** |
234
|
|
|
* Prepares the update query to upsert a given document object in mongodb. |
235
|
|
|
* |
236
|
|
|
* @param object $document |
237
|
|
|
* @return array $updateData |
238
|
|
|
*/ |
239
|
77 |
|
public function prepareUpsertData($document) |
240
|
|
|
{ |
241
|
77 |
|
$class = $this->dm->getClassMetadata(get_class($document)); |
242
|
77 |
|
$changeset = $this->uow->getDocumentChangeSet($document); |
243
|
|
|
|
244
|
77 |
|
$updateData = array(); |
245
|
77 |
|
foreach ($changeset as $fieldName => $change) { |
246
|
77 |
|
$mapping = $class->fieldMappings[$fieldName]; |
247
|
|
|
|
248
|
77 |
|
list($old, $new) = $change; |
249
|
|
|
|
250
|
|
|
// @Inc |
251
|
77 |
|
if ($mapping['type'] === 'increment') { |
252
|
4 |
|
if ($new >= $old) { |
253
|
4 |
|
$updateData['$inc'][$mapping['name']] = $new - $old; |
254
|
4 |
|
} else { |
255
|
|
|
$updateData['$inc'][$mapping['name']] = ($old - $new) * -1; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
// @Field, @String, @Date, etc. |
259
|
77 |
|
} elseif ( ! isset($mapping['association'])) { |
260
|
77 |
|
if (isset($new) || $mapping['nullable'] === true) { |
261
|
77 |
|
$updateData['$set'][$mapping['name']] = (is_null($new) ? null : Type::getType($mapping['type'])->convertToDatabaseValue($new)); |
262
|
77 |
|
} |
263
|
|
|
|
264
|
|
|
// @EmbedOne |
265
|
77 |
|
} elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::EMBED_ONE) { |
266
|
|
|
// If we have a new embedded document then lets set the whole thing |
267
|
3 |
|
if ($new && $this->uow->isScheduledForInsert($new)) { |
268
|
1 |
|
$updateData['$set'][$mapping['name']] = $this->prepareEmbeddedDocumentValue($mapping, $new); |
269
|
|
|
|
270
|
|
|
// If we don't have a new value then do nothing on upsert |
271
|
3 |
|
} elseif ( ! $new) { |
272
|
|
|
|
273
|
|
|
// Update existing embedded document |
274
|
2 |
View Code Duplication |
} else { |
|
|
|
|
275
|
|
|
$update = $this->prepareUpsertData($new); |
276
|
|
|
foreach ($update as $cmd => $values) { |
277
|
|
|
foreach ($values as $key => $value) { |
278
|
|
|
$updateData[$cmd][$mapping['name'] . '.' . $key] = $value; |
279
|
|
|
} |
280
|
|
|
} |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
// @ReferenceOne |
284
|
22 |
View Code Duplication |
} elseif (isset($mapping['association']) && $mapping['association'] === ClassMetadata::REFERENCE_ONE) { |
|
|
|
|
285
|
12 |
|
if (isset($new) || $mapping['nullable'] === true) { |
286
|
9 |
|
$updateData['$set'][$mapping['name']] = (is_null($new) ? null : $this->prepareReferencedDocumentValue($mapping, $new)); |
287
|
9 |
|
} |
288
|
|
|
|
289
|
|
|
// @ReferenceMany, @EmbedMany |
290
|
21 |
|
} elseif ($mapping['type'] === ClassMetadata::MANY && ! $mapping['isInverseSide'] |
291
|
13 |
|
&& $new instanceof PersistentCollection && $new->isDirty() |
292
|
13 |
|
&& CollectionHelper::isAtomic($mapping['strategy'])) { |
293
|
1 |
|
$updateData['$set'][$mapping['name']] = $this->prepareAssociatedCollectionValue($new, true); |
294
|
1 |
|
} |
295
|
|
|
// @EmbedMany and @ReferenceMany are handled by CollectionPersister |
296
|
77 |
|
} |
297
|
|
|
|
298
|
|
|
// add discriminator if the class has one |
299
|
77 |
View Code Duplication |
if (isset($class->discriminatorField)) { |
|
|
|
|
300
|
5 |
|
$updateData['$set'][$class->discriminatorField] = isset($class->discriminatorValue) |
301
|
5 |
|
? $class->discriminatorValue |
302
|
5 |
|
: $class->name; |
303
|
5 |
|
} |
304
|
|
|
|
305
|
77 |
|
return $updateData; |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* Returns the reference representation to be stored in MongoDB. |
310
|
|
|
* |
311
|
|
|
* If the document does not have an identifier and the mapping calls for a |
312
|
|
|
* simple reference, null may be returned. |
313
|
|
|
* |
314
|
|
|
* @param array $referenceMapping |
315
|
|
|
* @param object $document |
316
|
|
|
* @return array|null |
317
|
|
|
*/ |
318
|
190 |
|
public function prepareReferencedDocumentValue(array $referenceMapping, $document) |
319
|
|
|
{ |
320
|
190 |
|
return $this->dm->createDBRef($document, $referenceMapping); |
321
|
|
|
} |
322
|
|
|
|
323
|
|
|
/** |
324
|
|
|
* Returns the embedded document to be stored in MongoDB. |
325
|
|
|
* |
326
|
|
|
* The return value will usually be an associative array with string keys |
327
|
|
|
* corresponding to field names on the embedded document. An object may be |
328
|
|
|
* returned if the document is empty, to ensure that a BSON object will be |
329
|
|
|
* stored in lieu of an array. |
330
|
|
|
* |
331
|
|
|
* If $includeNestedCollections is true, nested collections will be included |
332
|
|
|
* in this prepared value and the option will cascade to all embedded |
333
|
|
|
* associations. If any nested PersistentCollections (embed or reference) |
334
|
|
|
* within this value were previously scheduled for deletion or update, they |
335
|
|
|
* will also be unscheduled. |
336
|
|
|
* |
337
|
|
|
* @param array $embeddedMapping |
338
|
|
|
* @param object $embeddedDocument |
339
|
|
|
* @param boolean $includeNestedCollections |
340
|
|
|
* @return array|object |
341
|
|
|
* @throws \UnexpectedValueException if an unsupported associating mapping is found |
342
|
|
|
*/ |
343
|
173 |
|
public function prepareEmbeddedDocumentValue(array $embeddedMapping, $embeddedDocument, $includeNestedCollections = false) |
344
|
|
|
{ |
345
|
173 |
|
$embeddedDocumentValue = array(); |
346
|
173 |
|
$class = $this->dm->getClassMetadata(get_class($embeddedDocument)); |
347
|
|
|
|
348
|
173 |
|
foreach ($class->fieldMappings as $mapping) { |
349
|
|
|
// Skip notSaved fields |
350
|
171 |
|
if ( ! empty($mapping['notSaved'])) { |
351
|
1 |
|
continue; |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
// Inline ClassMetadataInfo::getFieldValue() |
355
|
171 |
|
$rawValue = $class->reflFields[$mapping['fieldName']]->getValue($embeddedDocument); |
356
|
|
|
|
357
|
171 |
|
$value = null; |
358
|
|
|
|
359
|
171 |
|
if ($rawValue !== null) { |
360
|
168 |
|
switch (isset($mapping['association']) ? $mapping['association'] : null) { |
361
|
|
|
// @Field, @String, @Date, etc. |
362
|
168 |
|
case null: |
363
|
163 |
|
$value = Type::getType($mapping['type'])->convertToDatabaseValue($rawValue); |
364
|
163 |
|
break; |
365
|
|
|
|
366
|
64 |
|
case ClassMetadata::EMBED_ONE: |
367
|
64 |
|
case ClassMetadata::REFERENCE_ONE: |
368
|
|
|
// Nested collections should only be included for embedded relationships |
369
|
21 |
|
$value = $this->prepareAssociatedDocumentValue($mapping, $rawValue, $includeNestedCollections && isset($mapping['embedded'])); |
370
|
21 |
|
break; |
371
|
|
|
|
372
|
44 |
|
case ClassMetadata::EMBED_MANY: |
373
|
44 |
|
case ClassMetadata::REFERENCE_MANY: |
374
|
|
|
// Skip PersistentCollections already scheduled for deletion |
375
|
44 |
|
if ( ! $includeNestedCollections && $rawValue instanceof PersistentCollection |
376
|
44 |
|
&& $this->uow->isCollectionScheduledForDeletion($rawValue)) { |
377
|
|
|
break; |
378
|
|
|
} |
379
|
|
|
|
380
|
44 |
|
$value = $this->prepareAssociatedCollectionValue($rawValue, $includeNestedCollections); |
381
|
44 |
|
break; |
382
|
|
|
|
383
|
|
|
default: |
384
|
|
|
throw new \UnexpectedValueException('Unsupported mapping association: ' . $mapping['association']); |
385
|
168 |
|
} |
386
|
168 |
|
} |
387
|
|
|
|
388
|
|
|
// Omit non-nullable fields that would have a null value |
389
|
171 |
|
if ($value === null && $mapping['nullable'] === false) { |
390
|
60 |
|
continue; |
391
|
|
|
} |
392
|
|
|
|
393
|
168 |
|
$embeddedDocumentValue[$mapping['name']] = $value; |
394
|
173 |
|
} |
395
|
|
|
|
396
|
|
|
/* Add a discriminator value if the embedded document is not mapped |
397
|
|
|
* explicitly to a targetDocument class. |
398
|
|
|
*/ |
399
|
173 |
View Code Duplication |
if ( ! isset($embeddedMapping['targetDocument'])) { |
|
|
|
|
400
|
16 |
|
$discriminatorField = $embeddedMapping['discriminatorField']; |
401
|
16 |
|
$discriminatorValue = isset($embeddedMapping['discriminatorMap']) |
402
|
16 |
|
? array_search($class->name, $embeddedMapping['discriminatorMap']) |
403
|
16 |
|
: $class->name; |
404
|
|
|
|
405
|
|
|
/* If the discriminator value was not found in the map, use the full |
406
|
|
|
* class name. In the future, it may be preferable to throw an |
407
|
|
|
* exception here (perhaps based on some strictness option). |
408
|
|
|
* |
409
|
|
|
* @see DocumentManager::createDBRef() |
410
|
|
|
*/ |
411
|
16 |
|
if ($discriminatorValue === false) { |
412
|
2 |
|
$discriminatorValue = $class->name; |
413
|
2 |
|
} |
414
|
|
|
|
415
|
16 |
|
$embeddedDocumentValue[$discriminatorField] = $discriminatorValue; |
416
|
16 |
|
} |
417
|
|
|
|
418
|
|
|
/* If the class has a discriminator (field and value), use it. A child |
419
|
|
|
* class that is not defined in the discriminator map may only have a |
420
|
|
|
* discriminator field and no value, so default to the full class name. |
421
|
|
|
*/ |
422
|
173 |
View Code Duplication |
if (isset($class->discriminatorField)) { |
|
|
|
|
423
|
8 |
|
$embeddedDocumentValue[$class->discriminatorField] = isset($class->discriminatorValue) |
424
|
8 |
|
? $class->discriminatorValue |
425
|
8 |
|
: $class->name; |
426
|
8 |
|
} |
427
|
|
|
|
428
|
|
|
// Ensure empty embedded documents are stored as BSON objects |
429
|
173 |
|
if (empty($embeddedDocumentValue)) { |
430
|
6 |
|
return (object) $embeddedDocumentValue; |
431
|
|
|
} |
432
|
|
|
|
433
|
|
|
/* @todo Consider always casting the return value to an object, or |
434
|
|
|
* building $embeddedDocumentValue as an object instead of an array, to |
435
|
|
|
* handle the edge case where all database field names are sequential, |
436
|
|
|
* numeric keys. |
437
|
|
|
*/ |
438
|
169 |
|
return $embeddedDocumentValue; |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
/* |
442
|
|
|
* Returns the embedded document or reference representation to be stored. |
443
|
|
|
* |
444
|
|
|
* @param array $mapping |
445
|
|
|
* @param object $document |
446
|
|
|
* @param boolean $includeNestedCollections |
447
|
|
|
* @return array|object|null |
448
|
|
|
* @throws \InvalidArgumentException if the mapping is neither embedded nor reference |
449
|
|
|
*/ |
450
|
21 |
|
public function prepareAssociatedDocumentValue(array $mapping, $document, $includeNestedCollections = false) |
451
|
|
|
{ |
452
|
21 |
|
if (isset($mapping['embedded'])) { |
453
|
11 |
|
return $this->prepareEmbeddedDocumentValue($mapping, $document, $includeNestedCollections); |
454
|
|
|
} |
455
|
|
|
|
456
|
18 |
|
if (isset($mapping['reference'])) { |
457
|
18 |
|
return $this->prepareReferencedDocumentValue($mapping, $document); |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
throw new \InvalidArgumentException('Mapping is neither embedded nor reference.'); |
461
|
|
|
} |
462
|
|
|
|
463
|
|
|
/** |
464
|
|
|
* Returns the collection representation to be stored and unschedules it afterwards. |
465
|
|
|
* |
466
|
|
|
* @param PersistentCollection $coll |
467
|
|
|
* @param bool $includeNestedCollections |
468
|
|
|
* @return array |
469
|
|
|
*/ |
470
|
211 |
|
public function prepareAssociatedCollectionValue(PersistentCollection $coll, $includeNestedCollections = false) |
471
|
|
|
{ |
472
|
211 |
|
$mapping = $coll->getMapping(); |
473
|
211 |
|
$pb = $this; |
474
|
211 |
|
$callback = isset($mapping['embedded']) |
475
|
|
|
? function($v) use ($pb, $mapping, $includeNestedCollections) { |
476
|
117 |
|
return $pb->prepareEmbeddedDocumentValue($mapping, $v, $includeNestedCollections); |
477
|
|
|
} |
478
|
|
|
: function($v) use ($pb, $mapping) { return $pb->prepareReferencedDocumentValue($mapping, $v); }; |
479
|
|
|
|
480
|
211 |
|
$setData = $coll->map($callback)->toArray(); |
481
|
211 |
|
if (CollectionHelper::isList($mapping['strategy'])) { |
482
|
189 |
|
$setData = array_values($setData); |
483
|
189 |
|
} |
484
|
|
|
|
485
|
211 |
|
$this->uow->unscheduleCollectionDeletion($coll); |
486
|
211 |
|
$this->uow->unscheduleCollectionUpdate($coll); |
487
|
|
|
|
488
|
211 |
|
return $setData; |
489
|
|
|
} |
490
|
|
|
} |
491
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.