Completed
Push — master ( 7b9f4b...1cd743 )
by Andreas
13s queued 10s
created

lib/Doctrine/ODM/MongoDB/SchemaManager.php (2 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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