Completed
Pull Request — master (#1910)
by Andreas
20:32
created

SchemaManager::deleteDocumentIndexes()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4.074

Importance

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