Completed
Pull Request — master (#1911)
by Andreas
16:45
created

SchemaManager::prepareIndexes()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 15
cts 15
cp 1
rs 9.488
c 0
b 0
f 0
cc 4
nc 4
nop 1
crap 4
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 InvalidArgumentException;
10
use MongoDB\Driver\Exception\RuntimeException;
11
use MongoDB\Driver\Exception\ServerException;
12
use MongoDB\Model\IndexInfo;
13
use function array_diff_key;
14
use function array_filter;
15
use function array_unique;
16
use function iterator_count;
17
use function iterator_to_array;
18
use function ksort;
19
20
class SchemaManager
21
{
22
    private const GRIDFS_FILE_COLLECTION_INDEX = ['files_id' => 1, 'n' => 1];
23
24
    private const GRIDFS_CHUNKS_COLLECTION_INDEX = ['filename' => 1, 'uploadDate' => 1];
25
26
    private const CODE_SHARDING_ALREADY_INITIALIZED = 23;
27
28
    private const ALLOWED_MISSING_INDEX_OPTIONS = [
29
        'partialFilterExpression',
30
        'sparse',
31
        'unique',
32
        'weights',
33
        'default_language',
34
        'language_override',
35
        'textIndexVersion',
36
    ];
37
38
    /** @var DocumentManager */
39
    protected $dm;
40
41
    /** @var ClassMetadataFactory */
42
    protected $metadataFactory;
43
44 1709
    public function __construct(DocumentManager $dm, ClassMetadataFactory $cmf)
45
    {
46 1709
        $this->dm              = $dm;
47 1709
        $this->metadataFactory = $cmf;
48 1709
    }
49
50
    /**
51
     * Ensure indexes are created for all documents that can be loaded with the
52
     * metadata factory.
53
     */
54 1
    public function ensureIndexes(?int $timeout = null) : void
55
    {
56 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
57 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
58 1
                continue;
59
            }
60
61 1
            $this->ensureDocumentIndexes($class->name, $timeout);
62
        }
63 1
    }
64
65
    /**
66
     * Ensure indexes exist for all mapped document classes.
67
     *
68
     * Indexes that exist in MongoDB but not the document metadata will be
69
     * deleted.
70
     */
71
    public function updateIndexes(?int $timeout = null) : void
72
    {
73
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
74
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
75
                continue;
76
            }
77
78
            $this->updateDocumentIndexes($class->name, $timeout);
79
        }
80
    }
81
82
    /**
83
     * Ensure indexes exist for the mapped document class.
84
     *
85
     * Indexes that exist in MongoDB but not the document metadata will be
86
     * deleted.
87
     *
88
     * @throws InvalidArgumentException
89
     */
90 3
    public function updateDocumentIndexes(string $documentName, ?int $timeout = null) : void
91
    {
92 3
        $class = $this->dm->getClassMetadata($documentName);
93
94 3
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
95
            throw new InvalidArgumentException('Cannot update document indexes for mapped super classes, embedded documents or aggregation result documents.');
96
        }
97
98 3
        $documentIndexes = $this->getDocumentIndexes($documentName);
99 3
        $collection      = $this->dm->getDocumentCollection($documentName);
100 3
        $mongoIndexes    = iterator_to_array($collection->listIndexes());
101
102
        /* Determine which Mongo indexes should be deleted. Exclude the ID index
103
         * and those that are equivalent to any in the class metadata.
104
         */
105 3
        $self         = $this;
106
        $mongoIndexes = array_filter($mongoIndexes, static function (IndexInfo $mongoIndex) use ($documentIndexes, $self) {
107 1
            if ($mongoIndex['name'] === '_id_') {
108
                return false;
109
            }
110
111 1
            foreach ($documentIndexes as $documentIndex) {
112 1
                if ($self->isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)) {
113 1
                    return false;
114
                }
115
            }
116
117 1
            return true;
118 3
        });
119
120
        // Delete indexes that do not exist in class metadata
121 3
        foreach ($mongoIndexes as $mongoIndex) {
122 1
            if (! isset($mongoIndex['name'])) {
123
                continue;
124
            }
125
126 1
            $collection->dropIndex($mongoIndex['name']);
127
        }
128
129 3
        $this->ensureDocumentIndexes($documentName, $timeout);
130 3
    }
131
132 23
    public function getDocumentIndexes(string $documentName) : array
133
    {
134 23
        $visited = [];
135 23
        return $this->doGetDocumentIndexes($documentName, $visited);
136
    }
137
138 23
    private function doGetDocumentIndexes(string $documentName, array &$visited) : array
139
    {
140 23
        if (isset($visited[$documentName])) {
141 1
            return [];
142
        }
143
144 23
        $visited[$documentName] = true;
145
146 23
        $class                   = $this->dm->getClassMetadata($documentName);
147 23
        $indexes                 = $this->prepareIndexes($class);
148 23
        $embeddedDocumentIndexes = [];
149
150
        // Add indexes from embedded & referenced documents
151 23
        foreach ($class->fieldMappings as $fieldMapping) {
152 23
            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 23
            } elseif (isset($fieldMapping['reference']) && isset($fieldMapping['targetDocument'])) {
179 11
                foreach ($indexes as $idx => $index) {
180 10
                    $newKeys = [];
181 10
                    foreach ($index['keys'] as $key => $v) {
182 10
                        if ($key === $fieldMapping['name']) {
183 2
                            $key = ClassMetadata::getReferenceFieldName($fieldMapping['storeAs'], $key);
184
                        }
185
186 10
                        $newKeys[$key] = $v;
187
                    }
188
189 23
                    $indexes[$idx]['keys'] = $newKeys;
190
                }
191
            }
192
        }
193
194 23
        return $indexes;
195
    }
196
197 23
    private function prepareIndexes(ClassMetadata $class) : array
198
    {
199 23
        $persister  = $this->dm->getUnitOfWork()->getDocumentPersister($class->name);
200 23
        $indexes    = $class->getIndexes();
201 23
        $newIndexes = [];
202
203 23
        foreach ($indexes as $index) {
204
            $newIndex = [
205 22
                'keys' => [],
206 22
                'options' => $index['options'],
207
            ];
208
209 22
            foreach ($index['keys'] as $key => $value) {
210 22
                $key = $persister->prepareFieldName($key);
211 22
                if ($class->hasField($key)) {
212 17
                    $mapping                            = $class->getFieldMapping($key);
213 17
                    $newIndex['keys'][$mapping['name']] = $value;
214
                } else {
215 22
                    $newIndex['keys'][$key] = $value;
216
                }
217
            }
218
219 22
            $newIndexes[] = $newIndex;
220
        }
221
222 23
        return $newIndexes;
223
    }
224
225
    /**
226
     * Ensure the given document's indexes are created.
227
     *
228
     * @throws InvalidArgumentException
229
     */
230 19
    public function ensureDocumentIndexes(string $documentName, ?int $timeoutMs = null) : void
231
    {
232 19
        $class = $this->dm->getClassMetadata($documentName);
233 19
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
234
            throw new InvalidArgumentException('Cannot create document indexes for mapped super classes, embedded documents or query result documents.');
235
        }
236
237 19
        if ($class->isFile) {
238 2
            $this->ensureGridFSIndexes($class);
239
        }
240
241 19
        $indexes = $this->getDocumentIndexes($documentName);
242 19
        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...
243 4
            return;
244
        }
245
246 18
        $collection = $this->dm->getDocumentCollection($class->name);
247 18
        foreach ($indexes as $index) {
248 18
            $keys    = $index['keys'];
249 18
            $options = $index['options'];
250
251 18
            if (! isset($options['timeout']) && isset($timeoutMs)) {
252 1
                $options['timeout'] = $timeoutMs;
253
            }
254
255 18
            $collection->createIndex($keys, $options);
256
        }
257 18
    }
258
259
    /**
260
     * Delete indexes for all documents that can be loaded with the
261
     * metadata factory.
262
     */
263 1
    public function deleteIndexes() : void
264
    {
265 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
266 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
267 1
                continue;
268
            }
269
270 1
            $this->deleteDocumentIndexes($class->name);
271
        }
272 1
    }
273
274
    /**
275
     * Delete the given document's indexes.
276
     *
277
     * @throws InvalidArgumentException
278
     */
279 2
    public function deleteDocumentIndexes(string $documentName) : void
280
    {
281 2
        $class = $this->dm->getClassMetadata($documentName);
282 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
283
            throw new InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or query result documents.');
284
        }
285
286 2
        $this->dm->getDocumentCollection($documentName)->dropIndexes();
287 2
    }
288
289
    /**
290
     * Create all the mapped document collections in the metadata factory.
291
     */
292 1
    public function createCollections() : void
293
    {
294 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
295 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
296 1
                continue;
297
            }
298 1
            $this->createDocumentCollection($class->name);
299
        }
300 1
    }
301
302
    /**
303
     * Create the document collection for a mapped class.
304
     *
305
     * @throws InvalidArgumentException
306
     */
307 5
    public function createDocumentCollection(string $documentName) : void
308
    {
309 5
        $class = $this->dm->getClassMetadata($documentName);
310
311 5
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
312
            throw new InvalidArgumentException('Cannot create document collection for mapped super classes, embedded documents or query result documents.');
313
        }
314
315 5
        if ($class->isFile) {
316 2
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getBucketName() . '.files');
317 2
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getBucketName() . '.chunks');
318
319 2
            return;
320
        }
321
322 4
        $this->dm->getDocumentDatabase($documentName)->createCollection(
323 4
            $class->getCollection(),
324
            [
325 4
                'capped' => $class->getCollectionCapped(),
326 4
                'size' => $class->getCollectionSize(),
327 4
                'max' => $class->getCollectionMax(),
328
            ]
329
        );
330 4
    }
331
332
    /**
333
     * Drop all the mapped document collections in the metadata factory.
334
     */
335 1
    public function dropCollections() : void
336
    {
337 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
338 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
339 1
                continue;
340
            }
341
342 1
            $this->dropDocumentCollection($class->name);
343
        }
344 1
    }
345
346
    /**
347
     * Drop the document collection for a mapped class.
348
     *
349
     * @throws InvalidArgumentException
350
     */
351 5
    public function dropDocumentCollection(string $documentName) : void
352
    {
353 5
        $class = $this->dm->getClassMetadata($documentName);
354 5
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
355
            throw new InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or query result documents.');
356
        }
357
358 5
        $this->dm->getDocumentCollection($documentName)->drop();
359
360 5
        if (! $class->isFile) {
361 4
            return;
362
        }
363
364 2
        $this->dm->getDocumentBucket($documentName)->getChunksCollection()->drop();
365 2
    }
366
367
    /**
368
     * Drop all the mapped document databases in the metadata factory.
369
     */
370 1
    public function dropDatabases() : void
371
    {
372 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
373 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
374 1
                continue;
375
            }
376
377 1
            $this->dropDocumentDatabase($class->name);
378
        }
379 1
    }
380
381
    /**
382
     * Drop the document database for a mapped class.
383
     *
384
     * @throws InvalidArgumentException
385
     */
386 2
    public function dropDocumentDatabase(string $documentName) : void
387
    {
388 2
        $class = $this->dm->getClassMetadata($documentName);
389 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
390
            throw new InvalidArgumentException('Cannot drop document database for mapped super classes, embedded documents or query result documents.');
391
        }
392
393 2
        $this->dm->getDocumentDatabase($documentName)->drop();
394 2
    }
395
396 44
    public function isMongoIndexEquivalentToDocumentIndex(IndexInfo $mongoIndex, array $documentIndex) : bool
397
    {
398 44
        return $this->isEquivalentIndexKeys($mongoIndex, $documentIndex) && $this->isEquivalentIndexOptions($mongoIndex, $documentIndex);
399
    }
400
401
    /**
402
     * Determine if the keys for a MongoDB index can be considered equivalent to
403
     * those for an index in class metadata.
404
     */
405 44
    private function isEquivalentIndexKeys(IndexInfo $mongoIndex, array $documentIndex) : bool
406
    {
407 44
        $mongoIndexKeys    = $mongoIndex['key'];
408 44
        $documentIndexKeys = $documentIndex['keys'];
409
410
        /* If we are dealing with text indexes, we need to unset internal fields
411
         * from the MongoDB index and filter out text fields from the document
412
         * index. This will leave only non-text fields, which we can compare as
413
         * normal. Any text fields in the document index will be compared later
414
         * with isEquivalentTextIndexWeights(). */
415 44
        if (isset($mongoIndexKeys['_fts']) && $mongoIndexKeys['_fts'] === 'text') {
416 15
            unset($mongoIndexKeys['_fts'], $mongoIndexKeys['_ftsx']);
417
418
            $documentIndexKeys = array_filter($documentIndexKeys, static function ($type) {
419 15
                return $type !== 'text';
420 15
            });
421
        }
422
423
        /* Avoid a strict equality check here. The numeric type returned by
424
         * MongoDB may differ from the document index without implying that the
425
         * indexes themselves are inequivalent. */
426
        // phpcs:disable SlevomatCodingStandard.ControlStructures.DisallowEqualOperators.DisallowedEqualOperator
427 44
        return $mongoIndexKeys == $documentIndexKeys;
428
    }
429
430
    /**
431
     * Determine if an index returned by MongoCollection::getIndexInfo() can be
432
     * considered equivalent to an index in class metadata based on options.
433
     *
434
     * Indexes are considered different if:
435
     *
436
     *   (a) Key/direction pairs differ or are not in the same order
437
     *   (b) Sparse or unique options differ
438
     *   (c) Geospatial options differ (bits, max, min)
439
     *   (d) The partialFilterExpression differs
440
     */
441 40
    private function isEquivalentIndexOptions(IndexInfo $mongoIndex, array $documentIndex) : bool
442
    {
443 40
        $mongoIndexOptions = $mongoIndex->__debugInfo();
444 40
        unset($mongoIndexOptions['v'], $mongoIndexOptions['ns'], $mongoIndexOptions['key']);
445
446 40
        $documentIndexOptions = $documentIndex['options'];
447
448 40
        if ($this->indexOptionsAreMissing($mongoIndexOptions, $documentIndexOptions)) {
449 7
            return false;
450
        }
451
452 33
        if (empty($mongoIndexOptions['sparse']) xor empty($documentIndexOptions['sparse'])) {
453 2
            return false;
454
        }
455
456 31
        if (empty($mongoIndexOptions['unique']) xor empty($documentIndexOptions['unique'])) {
457 2
            return false;
458
        }
459
460 29
        foreach (['bits', 'max', 'min'] as $option) {
461 29
            if (isset($mongoIndexOptions[$option], $documentIndexOptions[$option]) &&
462 29
                $mongoIndexOptions[$option] !== $documentIndexOptions[$option]) {
463 29
                return false;
464
            }
465
        }
466
467 26
        if (empty($mongoIndexOptions['partialFilterExpression']) xor empty($documentIndexOptions['partialFilterExpression'])) {
468 2
            return false;
469
        }
470
471 24
        if (isset($mongoIndexOptions['partialFilterExpression'], $documentIndexOptions['partialFilterExpression']) &&
472 24
            $mongoIndexOptions['partialFilterExpression'] !== $documentIndexOptions['partialFilterExpression']) {
473 1
            return false;
474
        }
475
476 23
        if (isset($mongoIndexOptions['weights']) && ! $this->isEquivalentTextIndexWeights($mongoIndex, $documentIndex)) {
477 2
            return false;
478
        }
479
480 21
        foreach (['default_language', 'language_override', 'textIndexVersion'] as $option) {
481
            /* Text indexes will always report defaults for these options, so
482
             * only compare if we have explicit values in the document index. */
483 21
            if (isset($mongoIndexOptions[$option], $documentIndexOptions[$option]) &&
484 21
                $mongoIndexOptions[$option] !== $documentIndexOptions[$option]) {
485 21
                return false;
486
            }
487
        }
488
489 18
        return true;
490
    }
491
492
    /**
493
     * Checks if any index options are missing.
494
     *
495
     * Options added to the ALLOWED_MISSING_INDEX_OPTIONS constant are ignored
496
     * and are expected to be checked later
497
     */
498 40
    private function indexOptionsAreMissing(array $mongoIndexOptions, array $documentIndexOptions) : bool
499
    {
500 40
        foreach (self::ALLOWED_MISSING_INDEX_OPTIONS as $option) {
501 40
            unset($mongoIndexOptions[$option], $documentIndexOptions[$option]);
502
        }
503
504 40
        return array_diff_key($mongoIndexOptions, $documentIndexOptions) !== [] || array_diff_key($documentIndexOptions, $mongoIndexOptions) !== [];
505
    }
506
507
    /**
508
     * Determine if the text index weights for a MongoDB index can be considered
509
     * equivalent to those for an index in class metadata.
510
     */
511 14
    private function isEquivalentTextIndexWeights(IndexInfo $mongoIndex, array $documentIndex) : bool
512
    {
513 14
        $mongoIndexWeights    = $mongoIndex['weights'];
514 14
        $documentIndexWeights = $documentIndex['options']['weights'] ?? [];
515
516
        // If not specified, assign a default weight for text fields
517 14
        foreach ($documentIndex['keys'] as $key => $type) {
518 14
            if ($type !== 'text' || isset($documentIndexWeights[$key])) {
519 5
                continue;
520
            }
521
522 9
            $documentIndexWeights[$key] = 1;
523
        }
524
525
        /* MongoDB returns the weights sorted by field name, but we'll sort both
526
         * arrays in case that is internal behavior not to be relied upon. */
527 14
        ksort($mongoIndexWeights);
528 14
        ksort($documentIndexWeights);
529
530
        /* Avoid a strict equality check here. The numeric type returned by
531
         * MongoDB may differ from the document index without implying that the
532
         * indexes themselves are inequivalent. */
533
        // phpcs:disable SlevomatCodingStandard.ControlStructures.DisallowEqualOperators.DisallowedEqualOperator
534 14
        return $mongoIndexWeights == $documentIndexWeights;
535
    }
536
537
    /**
538
     * Ensure collections are sharded for all documents that can be loaded with the
539
     * metadata factory.
540
     *
541
     * @throws MongoDBException
542
     */
543
    public function ensureSharding() : void
544
    {
545
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
546
            if ($class->isMappedSuperclass || ! $class->isSharded()) {
547
                continue;
548
            }
549
550
            $this->ensureDocumentSharding($class->name);
551
        }
552
    }
553
554
    /**
555
     * Ensure sharding for collection by document name.
556
     *
557
     * @throws MongoDBException
558
     */
559 11
    public function ensureDocumentSharding(string $documentName) : void
560
    {
561 11
        $class = $this->dm->getClassMetadata($documentName);
562 11
        if (! $class->isSharded()) {
563
            return;
564
        }
565
566 11
        if ($this->collectionIsSharded($documentName)) {
567 1
            return;
568
        }
569
570 11
        $this->enableShardingForDbByDocumentName($documentName);
571
572
        try {
573 11
            $this->runShardCollectionCommand($documentName);
574 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...
575 1
            throw MongoDBException::failedToEnsureDocumentSharding($documentName, $e->getMessage());
576
        }
577 10
    }
578
579
    /**
580
     * Enable sharding for database which contains documents with given name.
581
     *
582
     * @throws MongoDBException
583
     */
584 11
    public function enableShardingForDbByDocumentName(string $documentName) : void
585
    {
586 11
        $dbName  = $this->dm->getDocumentDatabase($documentName)->getDatabaseName();
587 11
        $adminDb = $this->dm->getClient()->selectDatabase('admin');
588
589
        try {
590 11
            $adminDb->command(['enableSharding' => $dbName]);
591 10
        } catch (ServerException $e) {
0 ignored issues
show
Bug introduced by
The class MongoDB\Driver\Exception\ServerException 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...
592
            // Don't throw an exception if sharding is already enabled; there's just no other way to check this
593 10
            if ($e->getCode() !== self::CODE_SHARDING_ALREADY_INITIALIZED) {
594 10
                throw MongoDBException::failedToEnableSharding($dbName, $e->getMessage());
595
            }
596
        } 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...
597
            throw MongoDBException::failedToEnableSharding($dbName, $e->getMessage());
598
        }
599 11
    }
600
601 11
    private function runShardCollectionCommand(string $documentName) : array
602
    {
603 11
        $class    = $this->dm->getClassMetadata($documentName);
604 11
        $dbName   = $this->dm->getDocumentDatabase($documentName)->getDatabaseName();
605 11
        $shardKey = $class->getShardKey();
606 11
        $adminDb  = $this->dm->getClient()->selectDatabase('admin');
607
608 11
        return $adminDb->command(
609
            [
610 11
                'shardCollection' => $dbName . '.' . $class->getCollection(),
611 11
                'key'             => $shardKey['keys'],
612
            ]
613 10
        )->toArray()[0];
614
    }
615
616 2
    private function ensureGridFSIndexes(ClassMetadata $class) : void
617
    {
618 2
        $this->ensureChunksIndex($class);
619 2
        $this->ensureFilesIndex($class);
620 2
    }
621
622 2
    private function ensureChunksIndex(ClassMetadata $class) : void
623
    {
624 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...
625 2
        foreach ($chunksCollection->listIndexes() as $index) {
626
            if ($index->isUnique() && $index->getKey() === self::GRIDFS_FILE_COLLECTION_INDEX) {
627
                return;
628
            }
629
        }
630
631 2
        $chunksCollection->createIndex(self::GRIDFS_FILE_COLLECTION_INDEX, ['unique' => true]);
632 2
    }
633
634 2
    private function ensureFilesIndex(ClassMetadata $class) : void
635
    {
636 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...
637 2
        foreach ($filesCollection->listIndexes() as $index) {
638
            if ($index->getKey() === self::GRIDFS_CHUNKS_COLLECTION_INDEX) {
639
                return;
640
            }
641
        }
642
643 2
        $filesCollection->createIndex(self::GRIDFS_CHUNKS_COLLECTION_INDEX);
644 2
    }
645
646 11
    private function collectionIsSharded(string $documentName) : bool
647
    {
648 11
        $class = $this->dm->getClassMetadata($documentName);
649
650 11
        $database    = $this->dm->getDocumentDatabase($documentName);
651 11
        $collections = $database->listCollections(['filter' => ['name' => $class->getCollection()]]);
652 11
        if (! iterator_count($collections)) {
653 7
            return false;
654
        }
655
656 4
        $stats = $database->command(['collstats' => $class->getCollection()])->toArray()[0];
657 4
        return (bool) ($stats['sharded'] ?? false);
658
    }
659
}
660