Completed
Push — master ( f0fe3a...13ea99 )
by Andreas
12s
created

SchemaManager::ensureChunksIndex()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4.3731

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 5
cts 7
cp 0.7143
rs 9.9
c 0
b 0
f 0
cc 4
nc 3
nop 1
crap 4.3731
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 ksort;
15
use function strpos;
16
17
class SchemaManager
18
{
19
    private const GRIDFS_FILE_COLLECTION_INDEX = ['files_id' => 1, 'n' => 1];
20
21
    private const GRIDFS_CHUNKS_COLLECTION_INDEX = ['filename' => 1, 'uploadDate' => 1];
22
23
    /** @var DocumentManager */
24
    protected $dm;
25
26
    /** @var ClassMetadataFactory */
27
    protected $metadataFactory;
28
29 1665
    public function __construct(DocumentManager $dm, ClassMetadataFactory $cmf)
30
    {
31 1665
        $this->dm = $dm;
32 1665
        $this->metadataFactory = $cmf;
33 1665
    }
34
35
    /**
36
     * Ensure indexes are created for all documents that can be loaded with the
37
     * metadata factory.
38
     *
39
     * @param int $timeout Timeout (ms) for acknowledged index creation
40
     */
41 1
    public function ensureIndexes($timeout = null)
42
    {
43 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
44 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
45 1
                continue;
46
            }
47
48 1
            $this->ensureDocumentIndexes($class->name, $timeout);
49
        }
50 1
    }
51
52
    /**
53
     * Ensure indexes exist for all mapped document classes.
54
     *
55
     * Indexes that exist in MongoDB but not the document metadata will be
56
     * deleted.
57
     *
58
     * @param int $timeout Timeout (ms) for acknowledged index creation
59
     */
60
    public function updateIndexes($timeout = null)
61
    {
62
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
63
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
64
                continue;
65
            }
66
67
            $this->updateDocumentIndexes($class->name, $timeout);
68
        }
69
    }
70
71
    /**
72
     * Ensure indexes exist for the mapped document class.
73
     *
74
     * Indexes that exist in MongoDB but not the document metadata will be
75
     * deleted.
76
     *
77
     * @param string $documentName
78
     * @param int    $timeout      Timeout (ms) for acknowledged index creation
79
     * @throws \InvalidArgumentException
80
     */
81 3
    public function updateDocumentIndexes($documentName, $timeout = null)
82
    {
83 3
        $class = $this->dm->getClassMetadata($documentName);
84
85 3
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
86
            throw new \InvalidArgumentException('Cannot update document indexes for mapped super classes, embedded documents or aggregation result documents.');
87
        }
88
89 3
        $documentIndexes = $this->getDocumentIndexes($documentName);
90 3
        $collection = $this->dm->getDocumentCollection($documentName);
91 3
        $mongoIndexes = iterator_to_array($collection->listIndexes());
92
93
        /* Determine which Mongo indexes should be deleted. Exclude the ID index
94
         * and those that are equivalent to any in the class metadata.
95
         */
96 3
        $self = $this;
97
        $mongoIndexes = array_filter($mongoIndexes, function (IndexInfo $mongoIndex) use ($documentIndexes, $self) {
98 1
            if ($mongoIndex['name'] === '_id_') {
99
                return false;
100
            }
101
102 1
            foreach ($documentIndexes as $documentIndex) {
103 1
                if ($self->isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)) {
104 1
                    return false;
105
                }
106
            }
107
108 1
            return true;
109 3
        });
110
111
        // Delete indexes that do not exist in class metadata
112 3
        foreach ($mongoIndexes as $mongoIndex) {
113 1
            if (! isset($mongoIndex['name'])) {
114
                continue;
115
            }
116
117 1
            $collection->dropIndex($mongoIndex['name']);
118
        }
119
120 3
        $this->ensureDocumentIndexes($documentName, $timeout);
121 3
    }
122
123
    /**
124
     * @param string $documentName
125
     * @return array
126
     */
127 20
    public function getDocumentIndexes($documentName)
128
    {
129 20
        $visited = [];
130 20
        return $this->doGetDocumentIndexes($documentName, $visited);
131
    }
132
133
    /**
134
     * @param string $documentName
135
     * @param array  $visited
136
     * @return array
137
     */
138 20
    private function doGetDocumentIndexes($documentName, array &$visited)
139
    {
140 20
        if (isset($visited[$documentName])) {
141 1
            return [];
142
        }
143
144 20
        $visited[$documentName] = true;
145
146 20
        $class = $this->dm->getClassMetadata($documentName);
147 20
        $indexes = $this->prepareIndexes($class);
148 20
        $embeddedDocumentIndexes = [];
149
150
        // Add indexes from embedded & referenced documents
151 20
        foreach ($class->fieldMappings as $fieldMapping) {
152 20
            if (isset($fieldMapping['embedded'])) {
153 4
                if (isset($fieldMapping['targetDocument'])) {
154 4
                    $possibleEmbeds = [$fieldMapping['targetDocument']];
155 2
                } elseif (isset($fieldMapping['discriminatorMap'])) {
156 1
                    $possibleEmbeds = array_unique($fieldMapping['discriminatorMap']);
157
                } else {
158 1
                    continue;
159
                }
160
161 4
                foreach ($possibleEmbeds as $embed) {
162 4
                    if (isset($embeddedDocumentIndexes[$embed])) {
163 2
                        $embeddedIndexes = $embeddedDocumentIndexes[$embed];
164
                    } else {
165 4
                        $embeddedIndexes = $this->doGetDocumentIndexes($embed, $visited);
166 4
                        $embeddedDocumentIndexes[$embed] = $embeddedIndexes;
167
                    }
168
169 4
                    foreach ($embeddedIndexes as $embeddedIndex) {
170 2
                        foreach ($embeddedIndex['keys'] as $key => $value) {
171 2
                            $embeddedIndex['keys'][$fieldMapping['name'] . '.' . $key] = $value;
172 2
                            unset($embeddedIndex['keys'][$key]);
173
                        }
174
175 4
                        $indexes[] = $embeddedIndex;
176
                    }
177
                }
178 20
            } elseif (isset($fieldMapping['reference']) && isset($fieldMapping['targetDocument'])) {
179 9
                foreach ($indexes as $idx => $index) {
180 8
                    $newKeys = [];
181 8
                    foreach ($index['keys'] as $key => $v) {
182 8
                        if ($key === $fieldMapping['name']) {
183 2
                            $key = ClassMetadata::getReferenceFieldName($fieldMapping['storeAs'], $key);
184
                        }
185
186 8
                        $newKeys[$key] = $v;
187
                    }
188
189 20
                    $indexes[$idx]['keys'] = $newKeys;
190
                }
191
            }
192
        }
193
194 20
        return $indexes;
195
    }
196
197
    /**
198
     * @return array
199
     */
200 20
    private function prepareIndexes(ClassMetadata $class)
201
    {
202 20
        $persister = $this->dm->getUnitOfWork()->getDocumentPersister($class->name);
203 20
        $indexes = $class->getIndexes();
204 20
        $newIndexes = [];
205
206 20
        foreach ($indexes as $index) {
207
            $newIndex = [
208 19
                'keys' => [],
209 19
                'options' => $index['options'],
210
            ];
211
212 19
            foreach ($index['keys'] as $key => $value) {
213 19
                $key = $persister->prepareFieldName($key);
214 19
                if ($class->hasField($key)) {
215 17
                    $mapping = $class->getFieldMapping($key);
216 17
                    $newIndex['keys'][$mapping['name']] = $value;
217
                } else {
218 19
                    $newIndex['keys'][$key] = $value;
219
                }
220
            }
221
222 19
            $newIndexes[] = $newIndex;
223
        }
224
225 20
        return $newIndexes;
226
    }
227
228
    /**
229
     * Ensure the given document's indexes are created.
230
     *
231
     * @param string $documentName
232
     * @param int    $timeout      Timeout (ms) for acknowledged index creation
233
     * @throws \InvalidArgumentException
234
     */
235 16
    public function ensureDocumentIndexes($documentName, $timeout = null)
236
    {
237 16
        $class = $this->dm->getClassMetadata($documentName);
238 16
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
239
            throw new \InvalidArgumentException('Cannot create document indexes for mapped super classes, embedded documents or query result documents.');
240
        }
241
242 16
        if ($class->isFile) {
243 2
            $this->ensureGridFSIndexes($class);
244
        }
245
246 16
        $indexes = $this->getDocumentIndexes($documentName);
247 16
        if (! $indexes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
248 4
            return;
249
        }
250
251 15
        $collection = $this->dm->getDocumentCollection($class->name);
252 15
        foreach ($indexes as $index) {
253 15
            $keys = $index['keys'];
254 15
            $options = $index['options'];
255
256 15
            if (! isset($options['timeout']) && isset($timeout)) {
257 1
                $options['timeout'] = $timeout;
258
            }
259
260 15
            $collection->createIndex($keys, $options);
261
        }
262 15
    }
263
264
    /**
265
     * Delete indexes for all documents that can be loaded with the
266
     * metadata factory.
267
     */
268 1
    public function deleteIndexes()
269
    {
270 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
271 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
272 1
                continue;
273
            }
274
275 1
            $this->deleteDocumentIndexes($class->name);
276
        }
277 1
    }
278
279
    /**
280
     * Delete the given document's indexes.
281
     *
282
     * @param string $documentName
283
     * @throws \InvalidArgumentException
284
     */
285 2
    public function deleteDocumentIndexes($documentName)
286
    {
287 2
        $class = $this->dm->getClassMetadata($documentName);
288 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
289
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or query result documents.');
290
        }
291
292 2
        $this->dm->getDocumentCollection($documentName)->dropIndexes();
293 2
    }
294
295
    /**
296
     * Create all the mapped document collections in the metadata factory.
297
     */
298 1
    public function createCollections()
299
    {
300 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
301 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
302 1
                continue;
303
            }
304 1
            $this->createDocumentCollection($class->name);
305
        }
306 1
    }
307
308
    /**
309
     * Create the document collection for a mapped class.
310
     *
311
     * @param string $documentName
312
     * @throws \InvalidArgumentException
313
     */
314 5
    public function createDocumentCollection($documentName)
315
    {
316 5
        $class = $this->dm->getClassMetadata($documentName);
317
318 5
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
319
            throw new \InvalidArgumentException('Cannot create document collection for mapped super classes, embedded documents or query result documents.');
320
        }
321
322 5
        if ($class->isFile) {
323 2
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getBucketName() . '.files');
324 2
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getBucketName() . '.chunks');
325
326 2
            return;
327
        }
328
329 4
        $this->dm->getDocumentDatabase($documentName)->createCollection(
330 4
            $class->getCollection(),
331
            [
332 4
                'capped' => $class->getCollectionCapped(),
333 4
                'size' => $class->getCollectionSize(),
334 4
                'max' => $class->getCollectionMax(),
335
            ]
336
        );
337 4
    }
338
339
    /**
340
     * Drop all the mapped document collections in the metadata factory.
341
     */
342 1
    public function dropCollections()
343
    {
344 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
345 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
346 1
                continue;
347
            }
348
349 1
            $this->dropDocumentCollection($class->name);
350
        }
351 1
    }
352
353
    /**
354
     * Drop the document collection for a mapped class.
355
     *
356
     * @param string $documentName
357
     * @throws \InvalidArgumentException
358
     */
359 5
    public function dropDocumentCollection($documentName)
360
    {
361 5
        $class = $this->dm->getClassMetadata($documentName);
362 5
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
363
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or query result documents.');
364
        }
365
366 5
        $this->dm->getDocumentCollection($documentName)->drop();
367
368 5
        if (! $class->isFile) {
369 4
            return;
370
        }
371
372 2
        $this->dm->getDocumentBucket($documentName)->getChunksCollection()->drop();
373 2
    }
374
375
    /**
376
     * Drop all the mapped document databases in the metadata factory.
377
     */
378 1
    public function dropDatabases()
379
    {
380 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
381 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
382 1
                continue;
383
            }
384
385 1
            $this->dropDocumentDatabase($class->name);
386
        }
387 1
    }
388
389
    /**
390
     * Drop the document database for a mapped class.
391
     *
392
     * @param string $documentName
393
     * @throws \InvalidArgumentException
394
     */
395 2
    public function dropDocumentDatabase($documentName)
396
    {
397 2
        $class = $this->dm->getClassMetadata($documentName);
398 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
399
            throw new \InvalidArgumentException('Cannot drop document database for mapped super classes, embedded documents or query result documents.');
400
        }
401
402 2
        $this->dm->getDocumentDatabase($documentName)->drop();
403 2
    }
404
405
    /**
406
     * Determine if an index returned by MongoCollection::getIndexInfo() can be
407
     * considered equivalent to an index in class metadata.
408
     *
409
     * Indexes are considered different if:
410
     *
411
     *   (a) Key/direction pairs differ or are not in the same order
412
     *   (b) Sparse or unique options differ
413
     *   (c) Mongo index is unique without dropDups and mapped index is unique
414
     *       with dropDups
415
     *   (d) Geospatial options differ (bits, max, min)
416
     *   (e) The partialFilterExpression differs
417
     *
418
     * Regarding (c), the inverse case is not a reason to delete and
419
     * recreate the index, since dropDups only affects creation of
420
     * the unique index. Additionally, the background option is only
421
     * relevant to index creation and is not considered.
422
     *
423
     * @param array|IndexInfo $mongoIndex    Mongo index data.
424
     * @param array           $documentIndex Document index data.
425
     * @return bool True if the indexes are equivalent, otherwise false.
426
     */
427 47
    public function isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)
428
    {
429 47
        $documentIndexOptions = $documentIndex['options'];
430
431 47
        if (! $this->isEquivalentIndexKeys($mongoIndex, $documentIndex)) {
432 4
            return false;
433
        }
434
435 43
        if (empty($mongoIndex['sparse']) xor empty($documentIndexOptions['sparse'])) {
436 2
            return false;
437
        }
438
439 41
        if (empty($mongoIndex['unique']) xor empty($documentIndexOptions['unique'])) {
440 2
            return false;
441
        }
442
443 39
        if (! empty($mongoIndex['unique']) && empty($mongoIndex['dropDups']) &&
444 39
            ! empty($documentIndexOptions['unique']) && ! empty($documentIndexOptions['dropDups'])) {
445 1
            return false;
446
        }
447
448 38
        foreach (['bits', 'max', 'min'] as $option) {
449 38
            if (isset($mongoIndex[$option]) xor isset($documentIndexOptions[$option])) {
450 6
                return false;
451
            }
452
453 36
            if (isset($mongoIndex[$option], $documentIndexOptions[$option]) &&
454 36
                $mongoIndex[$option] !== $documentIndexOptions[$option]) {
455 36
                return false;
456
            }
457
        }
458
459 29
        if (empty($mongoIndex['partialFilterExpression']) xor empty($documentIndexOptions['partialFilterExpression'])) {
460 2
            return false;
461
        }
462
463 27
        if (isset($mongoIndex['partialFilterExpression'], $documentIndexOptions['partialFilterExpression']) &&
464 27
            $mongoIndex['partialFilterExpression'] !== $documentIndexOptions['partialFilterExpression']) {
465 1
            return false;
466
        }
467
468 26
        if (isset($mongoIndex['weights']) && ! $this->isEquivalentTextIndexWeights($mongoIndex, $documentIndex)) {
469 2
            return false;
470
        }
471
472 24
        foreach (['default_language', 'language_override', 'textIndexVersion'] as $option) {
473
            /* Text indexes will always report defaults for these options, so
474
             * only compare if we have explicit values in the document index. */
475 24
            if (isset($mongoIndex[$option], $documentIndexOptions[$option]) &&
476 24
                $mongoIndex[$option] !== $documentIndexOptions[$option]) {
477 24
                return false;
478
            }
479
        }
480
481 21
        return true;
482
    }
483
484
    /**
485
     * Determine if the keys for a MongoDB index can be considered equivalent to
486
     * those for an index in class metadata.
487
     *
488
     * @param array|IndexInfo $mongoIndex    Mongo index data.
489
     * @param array           $documentIndex Document index data.
490
     * @return bool True if the indexes have equivalent keys, otherwise false.
491
     */
492 47
    private function isEquivalentIndexKeys($mongoIndex, array $documentIndex)
493
    {
494 47
        $mongoIndexKeys    = $mongoIndex['key'];
495 47
        $documentIndexKeys = $documentIndex['keys'];
496
497
        /* If we are dealing with text indexes, we need to unset internal fields
498
         * from the MongoDB index and filter out text fields from the document
499
         * index. This will leave only non-text fields, which we can compare as
500
         * normal. Any text fields in the document index will be compared later
501
         * with isEquivalentTextIndexWeights(). */
502 47
        if (isset($mongoIndexKeys['_fts']) && $mongoIndexKeys['_fts'] === 'text') {
503 15
            unset($mongoIndexKeys['_fts'], $mongoIndexKeys['_ftsx']);
504
505
            $documentIndexKeys = array_filter($documentIndexKeys, function ($type) {
506 15
                return $type !== 'text';
507 15
            });
508
        }
509
510
        /* Avoid a strict equality check here. The numeric type returned by
511
         * MongoDB may differ from the document index without implying that the
512
         * indexes themselves are inequivalent. */
513
        // phpcs:disable SlevomatCodingStandard.ControlStructures.DisallowEqualOperators.DisallowedEqualOperator
514 47
        return $mongoIndexKeys == $documentIndexKeys;
515
    }
516
517
    /**
518
     * Determine if the text index weights for a MongoDB index can be considered
519
     * equivalent to those for an index in class metadata.
520
     *
521
     * @param array|IndexInfo $mongoIndex    Mongo index data.
522
     * @param array           $documentIndex Document index data.
523
     * @return bool True if the indexes have equivalent weights, otherwise false.
524
     */
525 14
    private function isEquivalentTextIndexWeights($mongoIndex, array $documentIndex)
526
    {
527 14
        $mongoIndexWeights    = $mongoIndex['weights'];
528 14
        $documentIndexWeights = $documentIndex['options']['weights'] ?? [];
529
530
        // If not specified, assign a default weight for text fields
531 14
        foreach ($documentIndex['keys'] as $key => $type) {
532 14
            if ($type !== 'text' || isset($documentIndexWeights[$key])) {
533 5
                continue;
534
            }
535
536 9
            $documentIndexWeights[$key] = 1;
537
        }
538
539
        /* MongoDB returns the weights sorted by field name, but we'll sort both
540
         * arrays in case that is internal behavior not to be relied upon. */
541 14
        ksort($mongoIndexWeights);
542 14
        ksort($documentIndexWeights);
543
544
        /* Avoid a strict equality check here. The numeric type returned by
545
         * MongoDB may differ from the document index without implying that the
546
         * indexes themselves are inequivalent. */
547
        // phpcs:disable SlevomatCodingStandard.ControlStructures.DisallowEqualOperators.DisallowedEqualOperator
548 14
        return $mongoIndexWeights == $documentIndexWeights;
549
    }
550
551
    /**
552
     * Ensure collections are sharded for all documents that can be loaded with the
553
     * metadata factory.
554
     *
555
     * @param array $indexOptions Options for `ensureIndex` command. It's performed on an existing collections
556
     *
557
     * @throws MongoDBException
558
     */
559
    public function ensureSharding(array $indexOptions = [])
560
    {
561
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
562
            if ($class->isMappedSuperclass || ! $class->isSharded()) {
563
                continue;
564
            }
565
566
            $this->ensureDocumentSharding($class->name, $indexOptions);
567
        }
568
    }
569
570
    /**
571
     * Ensure sharding for collection by document name.
572
     *
573
     * @param string $documentName
574
     * @param array  $indexOptions Options for `ensureIndex` command. It's performed on an existing collections.
575
     *
576
     * @throws MongoDBException
577
     */
578 2
    public function ensureDocumentSharding($documentName, array $indexOptions = [])
579
    {
580 2
        $class = $this->dm->getClassMetadata($documentName);
581 2
        if (! $class->isSharded()) {
582
            return;
583
        }
584
585 2
        $this->enableShardingForDbByDocumentName($documentName);
586
587 2
        $try = 0;
588
        do {
589
            try {
590 2
                $result = $this->runShardCollectionCommand($documentName);
591 2
                $done = true;
592
593
                // Need to check error message because MongoDB 3.0 does not return a code for this error
594 2
                if (! (bool) $result['ok'] && strpos($result['errmsg'], 'please create an index that starts') !== false) {
595
                    // The proposed key is not returned when using mongo-php-adapter with ext-mongodb.
596
                    // See https://github.com/mongodb/mongo-php-driver/issues/296 for details
597
                    $key = $result['proposedKey'] ?? $this->dm->getClassMetadata($documentName)->getShardKey()['keys'];
598
599
                    $this->dm->getDocumentCollection($documentName)->ensureIndex($key, $indexOptions);
600 2
                    $done = false;
601
                }
602 1
            } catch (RuntimeException $e) {
0 ignored issues
show
Bug introduced by
The class MongoDB\Driver\Exception\RuntimeException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
603 1
                if ($e->getCode() === 20 || $e->getCode() === 23 || $e->getMessage() === 'already sharded') {
604 1
                    return;
605
                }
606
607
                throw $e;
608
            }
609 2
        } while (! $done && $try < 2);
0 ignored issues
show
introduced by
It seems like the condition $try < 2 is always satisfied by any possible value of $try. Are you sure you do not have a deadlock here?
Loading history...
610
611
        // Starting with MongoDB 3.2, this command returns code 20 when a collection is already sharded.
612
        // For older MongoDB versions, check the error message
613 2
        if ((bool) $result['ok'] || (isset($result['code']) && $result['code'] === 20) || $result['errmsg'] === 'already sharded') {
614 2
            return;
615
        }
616
617
        throw MongoDBException::failedToEnsureDocumentSharding($documentName, $result['errmsg']);
618
    }
619
620
    /**
621
     * Enable sharding for database which contains documents with given name.
622
     *
623
     * @param string $documentName
624
     *
625
     * @throws MongoDBException
626
     */
627 2
    public function enableShardingForDbByDocumentName($documentName)
628
    {
629 2
        $dbName = $this->dm->getDocumentDatabase($documentName)->getDatabaseName();
630 2
        $adminDb = $this->dm->getClient()->selectDatabase('admin');
631
632
        try {
633 2
            $adminDb->command(['enableSharding' => $dbName]);
634 1
        } catch (RuntimeException $e) {
0 ignored issues
show
Bug introduced by
The class MongoDB\Driver\Exception\RuntimeException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
635 1
            if ($e->getCode() !== 23 || $e->getMessage() === 'already enabled') {
636
                throw MongoDBException::failedToEnableSharding($dbName, $e->getMessage());
637
            }
638
        }
639 2
    }
640
641
    /**
642
     * @param string $documentName
643
     *
644
     * @return array
645
     */
646 2
    private function runShardCollectionCommand($documentName)
647
    {
648 2
        $class = $this->dm->getClassMetadata($documentName);
649 2
        $dbName = $this->dm->getDocumentDatabase($documentName)->getDatabaseName();
650 2
        $shardKey = $class->getShardKey();
651 2
        $adminDb = $this->dm->getClient()->selectDatabase('admin');
652
653 2
        $result = $adminDb->command(
654
            [
655 2
                'shardCollection' => $dbName . '.' . $class->getCollection(),
656 2
                'key'             => $shardKey['keys'],
657
            ]
658 2
        )->toArray()[0];
659
660 2
        return $result;
661
    }
662
663 2
    private function ensureGridFSIndexes(ClassMetadata $class): void
664
    {
665 2
        $this->ensureChunksIndex($class);
666 2
        $this->ensureFilesIndex($class);
667 2
    }
668
669 2
    private function ensureChunksIndex(ClassMetadata $class): void
670
    {
671 2
        $chunksCollection = $this->dm->getDocumentBucket($class->getName())->getChunksCollection();
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
672 2
        foreach ($chunksCollection->listIndexes() as $index) {
673
            if ($index->isUnique() && $index->getKey() === self::GRIDFS_FILE_COLLECTION_INDEX) {
674
                return;
675
            }
676
        }
677
678 2
        $chunksCollection->createIndex(self::GRIDFS_FILE_COLLECTION_INDEX, ['unique' => true]);
679 2
    }
680
681 2
    private function ensureFilesIndex(ClassMetadata $class): void
682
    {
683 2
        $filesCollection = $this->dm->getDocumentCollection($class->getName());
0 ignored issues
show
Bug introduced by
Consider using $class->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
684 2
        foreach ($filesCollection->listIndexes() as $index) {
685
            if ($index->getKey() === self::GRIDFS_CHUNKS_COLLECTION_INDEX) {
686
                return;
687
            }
688
        }
689
690 2
        $filesCollection->createIndex(self::GRIDFS_CHUNKS_COLLECTION_INDEX);
691 2
    }
692
}
693