Completed
Push — master ( aa4018...235134 )
by Andreas
12s
created

SchemaManager::collectionIsSharded()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

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