Completed
Pull Request — master (#1848)
by Andreas
16:06 queued 14:06
created

SchemaManager::createCollections()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5

Importance

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