Completed
Pull Request — master (#1803)
by Maciej
20:22
created

SchemaManager::updateDocumentIndexes()   B

Complexity

Conditions 9
Paths 4

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 9.2363

Importance

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