1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Doctrine\ODM\MongoDB; |
6
|
|
|
|
7
|
|
|
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata; |
8
|
|
|
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory; |
9
|
|
|
use MongoDB\Driver\Exception\RuntimeException; |
10
|
|
|
use MongoDB\Model\IndexInfo; |
11
|
|
|
use function array_filter; |
12
|
|
|
use function array_unique; |
13
|
|
|
use function iterator_to_array; |
14
|
|
|
use function strpos; |
15
|
|
|
|
16
|
|
|
class SchemaManager |
17
|
|
|
{ |
18
|
|
|
/** @var DocumentManager */ |
19
|
|
|
protected $dm; |
20
|
|
|
|
21
|
|
|
/** @var ClassMetadataFactory */ |
22
|
|
|
protected $metadataFactory; |
23
|
|
|
|
24
|
1627 |
|
public function __construct(DocumentManager $dm, ClassMetadataFactory $cmf) |
25
|
|
|
{ |
26
|
1627 |
|
$this->dm = $dm; |
27
|
1627 |
|
$this->metadataFactory = $cmf; |
28
|
1627 |
|
} |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Ensure indexes are created for all documents that can be loaded with the |
32
|
|
|
* metadata factory. |
33
|
|
|
* |
34
|
|
|
* @param int $timeout Timeout (ms) for acknowledged index creation |
35
|
|
|
*/ |
36
|
1 |
View Code Duplication |
public function ensureIndexes($timeout = null) |
|
|
|
|
37
|
|
|
{ |
38
|
1 |
|
foreach ($this->metadataFactory->getAllMetadata() as $class) { |
39
|
1 |
|
if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
40
|
1 |
|
continue; |
41
|
|
|
} |
42
|
1 |
|
$this->ensureDocumentIndexes($class->name, $timeout); |
43
|
|
|
} |
44
|
1 |
|
} |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* Ensure indexes exist for all mapped document classes. |
48
|
|
|
* |
49
|
|
|
* Indexes that exist in MongoDB but not the document metadata will be |
50
|
|
|
* deleted. |
51
|
|
|
* |
52
|
|
|
* @param int $timeout Timeout (ms) for acknowledged index creation |
53
|
|
|
*/ |
54
|
|
View Code Duplication |
public function updateIndexes($timeout = null) |
|
|
|
|
55
|
|
|
{ |
56
|
|
|
foreach ($this->metadataFactory->getAllMetadata() as $class) { |
57
|
|
|
if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
58
|
|
|
continue; |
59
|
|
|
} |
60
|
|
|
$this->updateDocumentIndexes($class->name, $timeout); |
61
|
|
|
} |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Ensure indexes exist for the mapped document class. |
66
|
|
|
* |
67
|
|
|
* Indexes that exist in MongoDB but not the document metadata will be |
68
|
|
|
* deleted. |
69
|
|
|
* |
70
|
|
|
* @param string $documentName |
71
|
|
|
* @param int $timeout Timeout (ms) for acknowledged index creation |
72
|
|
|
* @throws \InvalidArgumentException |
73
|
|
|
*/ |
74
|
3 |
|
public function updateDocumentIndexes($documentName, $timeout = null) |
75
|
|
|
{ |
76
|
3 |
|
$class = $this->dm->getClassMetadata($documentName); |
77
|
|
|
|
78
|
3 |
|
if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
79
|
|
|
throw new \InvalidArgumentException('Cannot update document indexes for mapped super classes, embedded documents or aggregation result documents.'); |
80
|
|
|
} |
81
|
|
|
|
82
|
3 |
|
$documentIndexes = $this->getDocumentIndexes($documentName); |
83
|
3 |
|
$collection = $this->dm->getDocumentCollection($documentName); |
84
|
3 |
|
$mongoIndexes = iterator_to_array($collection->listIndexes()); |
85
|
|
|
|
86
|
|
|
/* Determine which Mongo indexes should be deleted. Exclude the ID index |
87
|
|
|
* and those that are equivalent to any in the class metadata. |
88
|
|
|
*/ |
89
|
3 |
|
$self = $this; |
90
|
|
|
$mongoIndexes = array_filter($mongoIndexes, function (IndexInfo $mongoIndex) use ($documentIndexes, $self) { |
91
|
1 |
|
if ($mongoIndex['name'] === '_id_') { |
92
|
|
|
return false; |
93
|
|
|
} |
94
|
|
|
|
95
|
1 |
|
foreach ($documentIndexes as $documentIndex) { |
96
|
1 |
|
if ($self->isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)) { |
97
|
1 |
|
return false; |
98
|
|
|
} |
99
|
|
|
} |
100
|
|
|
|
101
|
1 |
|
return true; |
102
|
3 |
|
}); |
103
|
|
|
|
104
|
|
|
// Delete indexes that do not exist in class metadata |
105
|
3 |
|
foreach ($mongoIndexes as $mongoIndex) { |
106
|
1 |
|
if (! isset($mongoIndex['name'])) { |
107
|
|
|
continue; |
108
|
|
|
} |
109
|
|
|
|
110
|
1 |
|
$collection->dropIndex($mongoIndex['name']); |
111
|
|
|
} |
112
|
|
|
|
113
|
3 |
|
$this->ensureDocumentIndexes($documentName, $timeout); |
114
|
3 |
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* @param string $documentName |
118
|
|
|
* @return array |
119
|
|
|
*/ |
120
|
19 |
|
public function getDocumentIndexes($documentName) |
121
|
|
|
{ |
122
|
19 |
|
$visited = []; |
123
|
19 |
|
return $this->doGetDocumentIndexes($documentName, $visited); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* @param string $documentName |
128
|
|
|
* @param array $visited |
129
|
|
|
* @return array |
130
|
|
|
*/ |
131
|
19 |
|
private function doGetDocumentIndexes($documentName, array &$visited) |
132
|
|
|
{ |
133
|
19 |
|
if (isset($visited[$documentName])) { |
134
|
1 |
|
return []; |
135
|
|
|
} |
136
|
|
|
|
137
|
19 |
|
$visited[$documentName] = true; |
138
|
|
|
|
139
|
19 |
|
$class = $this->dm->getClassMetadata($documentName); |
140
|
19 |
|
$indexes = $this->prepareIndexes($class); |
141
|
19 |
|
$embeddedDocumentIndexes = []; |
142
|
|
|
|
143
|
|
|
// Add indexes from embedded & referenced documents |
144
|
19 |
|
foreach ($class->fieldMappings as $fieldMapping) { |
145
|
19 |
|
if (isset($fieldMapping['embedded'])) { |
146
|
3 |
|
if (isset($fieldMapping['targetDocument'])) { |
147
|
3 |
|
$possibleEmbeds = [$fieldMapping['targetDocument']]; |
148
|
2 |
|
} elseif (isset($fieldMapping['discriminatorMap'])) { |
149
|
1 |
|
$possibleEmbeds = array_unique($fieldMapping['discriminatorMap']); |
150
|
|
|
} else { |
151
|
1 |
|
continue; |
152
|
|
|
} |
153
|
3 |
|
foreach ($possibleEmbeds as $embed) { |
154
|
3 |
|
if (isset($embeddedDocumentIndexes[$embed])) { |
155
|
2 |
|
$embeddedIndexes = $embeddedDocumentIndexes[$embed]; |
156
|
|
|
} else { |
157
|
3 |
|
$embeddedIndexes = $this->doGetDocumentIndexes($embed, $visited); |
158
|
3 |
|
$embeddedDocumentIndexes[$embed] = $embeddedIndexes; |
159
|
|
|
} |
160
|
3 |
|
foreach ($embeddedIndexes as $embeddedIndex) { |
161
|
2 |
|
foreach ($embeddedIndex['keys'] as $key => $value) { |
162
|
2 |
|
$embeddedIndex['keys'][$fieldMapping['name'] . '.' . $key] = $value; |
163
|
2 |
|
unset($embeddedIndex['keys'][$key]); |
164
|
|
|
} |
165
|
3 |
|
$indexes[] = $embeddedIndex; |
166
|
|
|
} |
167
|
|
|
} |
168
|
19 |
|
} elseif (isset($fieldMapping['reference']) && isset($fieldMapping['targetDocument'])) { |
169
|
8 |
|
foreach ($indexes as $idx => $index) { |
170
|
8 |
|
$newKeys = []; |
171
|
8 |
|
foreach ($index['keys'] as $key => $v) { |
172
|
8 |
|
if ($key === $fieldMapping['name']) { |
173
|
2 |
|
$key = ClassMetadata::getReferenceFieldName($fieldMapping['storeAs'], $key); |
174
|
|
|
} |
175
|
8 |
|
$newKeys[$key] = $v; |
176
|
|
|
} |
177
|
19 |
|
$indexes[$idx]['keys'] = $newKeys; |
178
|
|
|
} |
179
|
|
|
} |
180
|
|
|
} |
181
|
19 |
|
return $indexes; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* @return array |
186
|
|
|
*/ |
187
|
19 |
|
private function prepareIndexes(ClassMetadata $class) |
188
|
|
|
{ |
189
|
19 |
|
$persister = $this->dm->getUnitOfWork()->getDocumentPersister($class->name); |
190
|
19 |
|
$indexes = $class->getIndexes(); |
191
|
19 |
|
$newIndexes = []; |
192
|
|
|
|
193
|
19 |
|
foreach ($indexes as $index) { |
194
|
|
|
$newIndex = [ |
195
|
19 |
|
'keys' => [], |
196
|
19 |
|
'options' => $index['options'], |
197
|
|
|
]; |
198
|
19 |
|
foreach ($index['keys'] as $key => $value) { |
199
|
19 |
|
$key = $persister->prepareFieldName($key); |
200
|
19 |
|
if ($class->hasField($key)) { |
201
|
17 |
|
$mapping = $class->getFieldMapping($key); |
202
|
17 |
|
$newIndex['keys'][$mapping['name']] = $value; |
203
|
|
|
} else { |
204
|
19 |
|
$newIndex['keys'][$key] = $value; |
205
|
|
|
} |
206
|
|
|
} |
207
|
|
|
|
208
|
19 |
|
$newIndexes[] = $newIndex; |
209
|
|
|
} |
210
|
|
|
|
211
|
19 |
|
return $newIndexes; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Ensure the given document's indexes are created. |
216
|
|
|
* |
217
|
|
|
* @param string $documentName |
218
|
|
|
* @param int $timeout Timeout (ms) for acknowledged index creation |
219
|
|
|
* @throws \InvalidArgumentException |
220
|
|
|
*/ |
221
|
15 |
|
public function ensureDocumentIndexes($documentName, $timeout = null) |
222
|
|
|
{ |
223
|
15 |
|
$class = $this->dm->getClassMetadata($documentName); |
224
|
15 |
|
if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
225
|
|
|
throw new \InvalidArgumentException('Cannot create document indexes for mapped super classes, embedded documents or query result documents.'); |
226
|
|
|
} |
227
|
|
|
|
228
|
15 |
|
$indexes = $this->getDocumentIndexes($documentName); |
229
|
15 |
|
if (! $indexes) { |
|
|
|
|
230
|
3 |
|
return; |
231
|
|
|
} |
232
|
|
|
|
233
|
15 |
|
$collection = $this->dm->getDocumentCollection($class->name); |
234
|
15 |
|
foreach ($indexes as $index) { |
235
|
15 |
|
$keys = $index['keys']; |
236
|
15 |
|
$options = $index['options']; |
237
|
|
|
|
238
|
15 |
|
if (! isset($options['timeout']) && isset($timeout)) { |
239
|
1 |
|
$options['timeout'] = $timeout; |
240
|
|
|
} |
241
|
|
|
|
242
|
15 |
|
$collection->createIndex($keys, $options); |
243
|
|
|
} |
244
|
15 |
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* Delete indexes for all documents that can be loaded with the |
248
|
|
|
* metadata factory. |
249
|
|
|
*/ |
250
|
1 |
View Code Duplication |
public function deleteIndexes() |
|
|
|
|
251
|
|
|
{ |
252
|
1 |
|
foreach ($this->metadataFactory->getAllMetadata() as $class) { |
253
|
1 |
|
if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
254
|
1 |
|
continue; |
255
|
|
|
} |
256
|
1 |
|
$this->deleteDocumentIndexes($class->name); |
257
|
|
|
} |
258
|
1 |
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* Delete the given document's indexes. |
262
|
|
|
* |
263
|
|
|
* @param string $documentName |
264
|
|
|
* @throws \InvalidArgumentException |
265
|
|
|
*/ |
266
|
2 |
View Code Duplication |
public function deleteDocumentIndexes($documentName) |
|
|
|
|
267
|
|
|
{ |
268
|
2 |
|
$class = $this->dm->getClassMetadata($documentName); |
269
|
2 |
|
if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
270
|
|
|
throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or query result documents.'); |
271
|
|
|
} |
272
|
2 |
|
$this->dm->getDocumentCollection($documentName)->dropIndexes(); |
273
|
2 |
|
} |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* Create all the mapped document collections in the metadata factory. |
277
|
|
|
*/ |
278
|
1 |
View Code Duplication |
public function createCollections() |
|
|
|
|
279
|
|
|
{ |
280
|
1 |
|
foreach ($this->metadataFactory->getAllMetadata() as $class) { |
281
|
1 |
|
if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
282
|
1 |
|
continue; |
283
|
|
|
} |
284
|
1 |
|
$this->createDocumentCollection($class->name); |
285
|
|
|
} |
286
|
1 |
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* Create the document collection for a mapped class. |
290
|
|
|
* |
291
|
|
|
* @param string $documentName |
292
|
|
|
* @throws \InvalidArgumentException |
293
|
|
|
*/ |
294
|
4 |
|
public function createDocumentCollection($documentName) |
295
|
|
|
{ |
296
|
4 |
|
$class = $this->dm->getClassMetadata($documentName); |
297
|
|
|
|
298
|
4 |
|
if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
299
|
|
|
throw new \InvalidArgumentException('Cannot create document collection for mapped super classes, embedded documents or query result documents.'); |
300
|
|
|
} |
301
|
|
|
|
302
|
4 |
|
$this->dm->getDocumentDatabase($documentName)->createCollection( |
303
|
4 |
|
$class->getCollection(), |
304
|
|
|
[ |
305
|
4 |
|
'capped' => $class->getCollectionCapped(), |
306
|
4 |
|
'size' => $class->getCollectionSize(), |
307
|
4 |
|
'max' => $class->getCollectionMax(), |
308
|
|
|
] |
309
|
|
|
); |
310
|
4 |
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* Drop all the mapped document collections in the metadata factory. |
314
|
|
|
*/ |
315
|
1 |
View Code Duplication |
public function dropCollections() |
|
|
|
|
316
|
|
|
{ |
317
|
1 |
|
foreach ($this->metadataFactory->getAllMetadata() as $class) { |
318
|
1 |
|
if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
319
|
1 |
|
continue; |
320
|
|
|
} |
321
|
1 |
|
$this->dropDocumentCollection($class->name); |
322
|
|
|
} |
323
|
1 |
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* Drop the document collection for a mapped class. |
327
|
|
|
* |
328
|
|
|
* @param string $documentName |
329
|
|
|
* @throws \InvalidArgumentException |
330
|
|
|
*/ |
331
|
4 |
View Code Duplication |
public function dropDocumentCollection($documentName) |
|
|
|
|
332
|
|
|
{ |
333
|
4 |
|
$class = $this->dm->getClassMetadata($documentName); |
334
|
4 |
|
if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
335
|
|
|
throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or query result documents.'); |
336
|
|
|
} |
337
|
4 |
|
$this->dm->getDocumentCollection($documentName)->drop(); |
338
|
4 |
|
} |
339
|
|
|
|
340
|
|
|
/** |
341
|
|
|
* Drop all the mapped document databases in the metadata factory. |
342
|
|
|
*/ |
343
|
1 |
View Code Duplication |
public function dropDatabases() |
|
|
|
|
344
|
|
|
{ |
345
|
1 |
|
foreach ($this->metadataFactory->getAllMetadata() as $class) { |
346
|
1 |
|
if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
347
|
1 |
|
continue; |
348
|
|
|
} |
349
|
1 |
|
$this->dropDocumentDatabase($class->name); |
350
|
|
|
} |
351
|
1 |
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* Drop the document database for a mapped class. |
355
|
|
|
* |
356
|
|
|
* @param string $documentName |
357
|
|
|
* @throws \InvalidArgumentException |
358
|
|
|
*/ |
359
|
2 |
View Code Duplication |
public function dropDocumentDatabase($documentName) |
|
|
|
|
360
|
|
|
{ |
361
|
2 |
|
$class = $this->dm->getClassMetadata($documentName); |
362
|
2 |
|
if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
363
|
|
|
throw new \InvalidArgumentException('Cannot drop document database for mapped super classes, embedded documents or query result documents.'); |
364
|
|
|
} |
365
|
2 |
|
$this->dm->getDocumentDatabase($documentName)->drop(); |
366
|
2 |
|
} |
367
|
|
|
|
368
|
|
|
/** |
369
|
|
|
* Determine if an index returned by MongoCollection::getIndexInfo() can be |
370
|
|
|
* considered equivalent to an index in class metadata. |
371
|
|
|
* |
372
|
|
|
* Indexes are considered different if: |
373
|
|
|
* |
374
|
|
|
* (a) Key/direction pairs differ or are not in the same order |
375
|
|
|
* (b) Sparse or unique options differ |
376
|
|
|
* (c) Mongo index is unique without dropDups and mapped index is unique |
377
|
|
|
* with dropDups |
378
|
|
|
* (d) Geospatial options differ (bits, max, min) |
379
|
|
|
* (e) The partialFilterExpression differs |
380
|
|
|
* |
381
|
|
|
* Regarding (c), the inverse case is not a reason to delete and |
382
|
|
|
* recreate the index, since dropDups only affects creation of |
383
|
|
|
* the unique index. Additionally, the background option is only |
384
|
|
|
* relevant to index creation and is not considered. |
385
|
|
|
* |
386
|
|
|
* @param array|IndexInfo $mongoIndex Mongo index data. |
387
|
|
|
* @param array $documentIndex Document index data. |
388
|
|
|
* @return bool True if the indexes are equivalent, otherwise false. |
389
|
|
|
*/ |
390
|
30 |
|
public function isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex) |
391
|
|
|
{ |
392
|
30 |
|
$documentIndexOptions = $documentIndex['options']; |
393
|
|
|
|
394
|
30 |
|
if ($mongoIndex['key'] !== $documentIndex['keys']) { |
395
|
2 |
|
return false; |
396
|
|
|
} |
397
|
|
|
|
398
|
28 |
|
if (empty($mongoIndex['sparse']) xor empty($documentIndexOptions['sparse'])) { |
399
|
2 |
|
return false; |
400
|
|
|
} |
401
|
|
|
|
402
|
26 |
|
if (empty($mongoIndex['unique']) xor empty($documentIndexOptions['unique'])) { |
403
|
2 |
|
return false; |
404
|
|
|
} |
405
|
|
|
|
406
|
24 |
|
if (! empty($mongoIndex['unique']) && empty($mongoIndex['dropDups']) && |
407
|
24 |
|
! empty($documentIndexOptions['unique']) && ! empty($documentIndexOptions['dropDups'])) { |
408
|
1 |
|
return false; |
409
|
|
|
} |
410
|
|
|
|
411
|
23 |
|
foreach (['bits', 'max', 'min'] as $option) { |
412
|
23 |
|
if (isset($mongoIndex[$option]) xor isset($documentIndexOptions[$option])) { |
413
|
6 |
|
return false; |
414
|
|
|
} |
415
|
|
|
|
416
|
21 |
|
if (isset($mongoIndex[$option]) && isset($documentIndexOptions[$option]) && |
417
|
21 |
|
$mongoIndex[$option] !== $documentIndexOptions[$option]) { |
418
|
21 |
|
return false; |
419
|
|
|
} |
420
|
|
|
} |
421
|
|
|
|
422
|
14 |
|
if (empty($mongoIndex['partialFilterExpression']) xor empty($documentIndexOptions['partialFilterExpression'])) { |
423
|
2 |
|
return false; |
424
|
|
|
} |
425
|
|
|
|
426
|
12 |
|
if (isset($mongoIndex['partialFilterExpression']) && isset($documentIndexOptions['partialFilterExpression']) && |
427
|
12 |
|
$mongoIndex['partialFilterExpression'] !== $documentIndexOptions['partialFilterExpression']) { |
428
|
1 |
|
return false; |
429
|
|
|
} |
430
|
|
|
|
431
|
11 |
|
return true; |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
/** |
435
|
|
|
* Ensure collections are sharded for all documents that can be loaded with the |
436
|
|
|
* metadata factory. |
437
|
|
|
* |
438
|
|
|
* @param array $indexOptions Options for `ensureIndex` command. It's performed on an existing collections |
439
|
|
|
* |
440
|
|
|
* @throws MongoDBException |
441
|
|
|
*/ |
442
|
|
|
public function ensureSharding(array $indexOptions = []) |
443
|
|
|
{ |
444
|
|
|
foreach ($this->metadataFactory->getAllMetadata() as $class) { |
445
|
|
|
if ($class->isMappedSuperclass || ! $class->isSharded()) { |
446
|
|
|
continue; |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
$this->ensureDocumentSharding($class->name, $indexOptions); |
450
|
|
|
} |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
/** |
454
|
|
|
* Ensure sharding for collection by document name. |
455
|
|
|
* |
456
|
|
|
* @param string $documentName |
457
|
|
|
* @param array $indexOptions Options for `ensureIndex` command. It's performed on an existing collections. |
458
|
|
|
* |
459
|
|
|
* @throws MongoDBException |
460
|
|
|
*/ |
461
|
2 |
|
public function ensureDocumentSharding($documentName, array $indexOptions = []) |
462
|
|
|
{ |
463
|
2 |
|
$class = $this->dm->getClassMetadata($documentName); |
464
|
2 |
|
if (! $class->isSharded()) { |
465
|
|
|
return; |
466
|
|
|
} |
467
|
|
|
|
468
|
2 |
|
$this->enableShardingForDbByDocumentName($documentName); |
469
|
|
|
|
470
|
2 |
|
$try = 0; |
471
|
|
|
do { |
472
|
|
|
try { |
473
|
2 |
|
$result = $this->runShardCollectionCommand($documentName); |
474
|
2 |
|
$done = true; |
475
|
|
|
|
476
|
|
|
// Need to check error message because MongoDB 3.0 does not return a code for this error |
477
|
2 |
|
if (! (bool) $result['ok'] && strpos($result['errmsg'], 'please create an index that starts') !== false) { |
478
|
|
|
// The proposed key is not returned when using mongo-php-adapter with ext-mongodb. |
479
|
|
|
// See https://github.com/mongodb/mongo-php-driver/issues/296 for details |
480
|
|
|
$key = $result['proposedKey'] ?? $this->dm->getClassMetadata($documentName)->getShardKey()['keys']; |
481
|
|
|
|
482
|
|
|
$this->dm->getDocumentCollection($documentName)->ensureIndex($key, $indexOptions); |
483
|
2 |
|
$done = false; |
484
|
|
|
} |
485
|
1 |
|
} catch (RuntimeException $e) { |
|
|
|
|
486
|
1 |
|
if ($e->getCode() === 20 || $e->getCode() === 23 || $e->getMessage() === 'already sharded') { |
487
|
1 |
|
return; |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
throw $e; |
491
|
|
|
} |
492
|
2 |
|
} while (! $done && $try < 2); |
|
|
|
|
493
|
|
|
|
494
|
|
|
// Starting with MongoDB 3.2, this command returns code 20 when a collection is already sharded. |
495
|
|
|
// For older MongoDB versions, check the error message |
496
|
2 |
|
if ((bool) $result['ok'] || (isset($result['code']) && $result['code'] === 20) || $result['errmsg'] === 'already sharded') { |
497
|
2 |
|
return; |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
throw MongoDBException::failedToEnsureDocumentSharding($documentName, $result['errmsg']); |
501
|
|
|
} |
502
|
|
|
|
503
|
|
|
/** |
504
|
|
|
* Enable sharding for database which contains documents with given name. |
505
|
|
|
* |
506
|
|
|
* @param string $documentName |
507
|
|
|
* |
508
|
|
|
* @throws MongoDBException |
509
|
|
|
*/ |
510
|
2 |
|
public function enableShardingForDbByDocumentName($documentName) |
511
|
|
|
{ |
512
|
2 |
|
$dbName = $this->dm->getDocumentDatabase($documentName)->getDatabaseName(); |
513
|
2 |
|
$adminDb = $this->dm->getClient()->selectDatabase('admin'); |
514
|
|
|
|
515
|
|
|
try { |
516
|
2 |
|
$adminDb->command(['enableSharding' => $dbName]); |
517
|
1 |
|
} catch (RuntimeException $e) { |
|
|
|
|
518
|
1 |
|
if ($e->getCode() !== 23 || $e->getMessage() === 'already enabled') { |
519
|
|
|
throw MongoDBException::failedToEnableSharding($dbName, $e->getMessage()); |
520
|
|
|
} |
521
|
|
|
} |
522
|
2 |
|
} |
523
|
|
|
|
524
|
|
|
/** |
525
|
|
|
* @param string $documentName |
526
|
|
|
* |
527
|
|
|
* @return array |
528
|
|
|
*/ |
529
|
2 |
|
private function runShardCollectionCommand($documentName) |
530
|
|
|
{ |
531
|
2 |
|
$class = $this->dm->getClassMetadata($documentName); |
532
|
2 |
|
$dbName = $this->dm->getDocumentDatabase($documentName)->getDatabaseName(); |
533
|
2 |
|
$shardKey = $class->getShardKey(); |
534
|
2 |
|
$adminDb = $this->dm->getClient()->selectDatabase('admin'); |
535
|
|
|
|
536
|
2 |
|
$result = $adminDb->command( |
537
|
|
|
[ |
538
|
2 |
|
'shardCollection' => $dbName . '.' . $class->getCollection(), |
539
|
2 |
|
'key' => $shardKey['keys'], |
540
|
|
|
] |
541
|
2 |
|
)->toArray()[0]; |
542
|
|
|
|
543
|
2 |
|
return $result; |
544
|
|
|
} |
545
|
|
|
} |
546
|
|
|
|
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.