Completed
Push — master ( 8c0c5d...126e10 )
by Andreas
15s queued 10s
created

SchemaManager::getWriteOptions()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

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