Completed
Pull Request — master (#1848)
by Andreas
14:47 queued 12:11
created

SchemaManager::ensureFilesIndex()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3.2098

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