Completed
Pull Request — master (#1263)
by Andreas
13:28
created

SchemaManager::ensureIndexes()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 9
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 9
loc 9
ccs 0
cts 6
cp 0
rs 8.8571
cc 5
eloc 5
nc 3
nop 1
crap 30
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ODM\MongoDB;
21
22
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
23
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory;
24
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo;
25
26
class SchemaManager
27
{
28
    /**
29
     * @var DocumentManager
30
     */
31
    protected $dm;
32
33
    /**
34
     *
35
     * @var ClassMetadataFactory
36
     */
37
    protected $metadataFactory;
38
39
    /**
40
     * @param DocumentManager $dm
41
     * @param ClassMetadataFactory $cmf
42
     */
43 1000
    public function __construct(DocumentManager $dm, ClassMetadataFactory $cmf)
44
    {
45 1000
        $this->dm = $dm;
46 1000
        $this->metadataFactory = $cmf;
47 1000
    }
48
49
    /**
50
     * Ensure indexes are created for all documents that can be loaded with the
51
     * metadata factory.
52
     *
53
     * @param integer $timeout Timeout (ms) for acknowledged index creation
54
     */
55 View Code Duplication
    public function ensureIndexes($timeout = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
56
    {
57
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
58
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isAggregationResultDocument) {
59
                continue;
60
            }
61
            $this->ensureDocumentIndexes($class->name, $timeout);
62
        }
63
    }
64
65
    /**
66
     * Ensure indexes exist for all mapped document classes.
67
     *
68
     * Indexes that exist in MongoDB but not the document metadata will be
69
     * deleted.
70
     *
71
     * @param integer $timeout Timeout (ms) for acknowledged index creation
72
     */
73 View Code Duplication
    public function updateIndexes($timeout = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
74
    {
75
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
76
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isAggregationResultDocument) {
77
                continue;
78
            }
79
            $this->updateDocumentIndexes($class->name, $timeout);
80
        }
81
    }
82
83
    /**
84
     * Ensure indexes exist for the mapped document class.
85
     *
86
     * Indexes that exist in MongoDB but not the document metadata will be
87
     * deleted.
88
     *
89
     * @param string $documentName
90
     * @param integer $timeout Timeout (ms) for acknowledged index creation
91
     * @throws \InvalidArgumentException
92
     */
93 1
    public function updateDocumentIndexes($documentName, $timeout = null)
94
    {
95 1
        $class = $this->dm->getClassMetadata($documentName);
96
97 1
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isAggregationResultDocument) {
98
            throw new \InvalidArgumentException('Cannot update document indexes for mapped super classes, embedded documents or aggregation result documents.');
99
        }
100
101 1
        $documentIndexes = $this->getDocumentIndexes($documentName);
102 1
        $collection = $this->dm->getDocumentCollection($documentName);
103 1
        $mongoIndexes = $collection->getIndexInfo();
104
105
        /* Determine which Mongo indexes should be deleted. Exclude the ID index
106
         * and those that are equivalent to any in the class metadata.
107
         */
108 1
        $self = $this;
109 1
        $mongoIndexes = array_filter($mongoIndexes, function ($mongoIndex) use ($documentIndexes, $self) {
110
            if ('_id_' === $mongoIndex['name']) {
111
                return false;
112
            }
113
114
            foreach ($documentIndexes as $documentIndex) {
115
                if ($self->isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)) {
116
                    return false;
117
                }
118
            }
119
120
            return true;
121 1
        });
122
123
        // Delete indexes that do not exist in class metadata
124 1
        foreach ($mongoIndexes as $mongoIndex) {
125
            if (isset($mongoIndex['name'])) {
126
                /* Note: MongoCollection::deleteIndex() cannot delete
127
                 * custom-named indexes, so use the deleteIndexes command.
128
                 */
129
                $collection->getDatabase()->command(array(
130
                    'deleteIndexes' => $collection->getName(),
131
                    'index' => $mongoIndex['name'],
132
                ));
133
            }
134
        }
135
136 1
        $this->ensureDocumentIndexes($documentName, $timeout);
137 1
    }
138
139
    /**
140
     * @param string $documentName
141
     * @return array
142
     */
143 41
    public function getDocumentIndexes($documentName)
144
    {
145 41
        $visited = array();
146 41
        return $this->doGetDocumentIndexes($documentName, $visited);
147
    }
148
149
    /**
150
     * @param string $documentName
151
     * @param array $visited
152
     * @return array
153
     */
154 41
    private function doGetDocumentIndexes($documentName, array &$visited)
155
    {
156 41
        if (isset($visited[$documentName])) {
157
            return array();
158
        }
159
160 41
        $visited[$documentName] = true;
161
162 41
        $class = $this->dm->getClassMetadata($documentName);
163 41
        $indexes = $this->prepareIndexes($class);
164 41
        $embeddedDocumentIndexes = array();
165
166
        // Add indexes from embedded & referenced documents
167 41
        foreach ($class->fieldMappings as $fieldMapping) {
168 41
            if (isset($fieldMapping['embedded'])) {
169 30
                if (isset($fieldMapping['targetDocument'])) {
170 30
                    $possibleEmbeds = array($fieldMapping['targetDocument']);
171 1
                } elseif (isset($fieldMapping['discriminatorMap'])) {
172 1
                    $possibleEmbeds = array_unique($fieldMapping['discriminatorMap']);
173
                } else {
174
                    continue;
175
                }
176 30
                foreach ($possibleEmbeds as $embed) {
177 30
                    if (isset($embeddedDocumentIndexes[$embed])) {
178 24
                        $embeddedIndexes = $embeddedDocumentIndexes[$embed];
179
                    } else {
180 30
                        $embeddedIndexes = $this->doGetDocumentIndexes($embed, $visited);
181 30
                        $embeddedDocumentIndexes[$embed] = $embeddedIndexes;
182
                    }
183 30
                    foreach ($embeddedIndexes as $embeddedIndex) {
184 25
                        foreach ($embeddedIndex['keys'] as $key => $value) {
185 25
                            $embeddedIndex['keys'][$fieldMapping['name'] . '.' . $key] = $value;
186 25
                            unset($embeddedIndex['keys'][$key]);
187
                        }
188 30
                        $indexes[] = $embeddedIndex;
189
                    }
190
                }
191 41
            } elseif (isset($fieldMapping['reference']) && isset($fieldMapping['targetDocument'])) {
192 26
                foreach ($indexes as $idx => $index) {
193 26
                    $newKeys = array();
194 26
                    foreach ($index['keys'] as $key => $v) {
195 26
                        if ($key == $fieldMapping['name']) {
196 1
                            $key = $fieldMapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID
197 1
                                ? $key
198 1
                                : $key . '.$id';
199
                        }
200 26
                        $newKeys[$key] = $v;
201
                    }
202 41
                    $indexes[$idx]['keys'] = $newKeys;
203
                }
204
            }
205
        }
206 41
        return $indexes;
207
    }
208
209
    /**
210
     * @param ClassMetadata $class
211
     * @return array
212
     */
213 41
    private function prepareIndexes(ClassMetadata $class)
214
    {
215 41
        $persister = $this->dm->getUnitOfWork()->getDocumentPersister($class->name);
216 41
        $indexes = $class->getIndexes();
217 41
        $newIndexes = array();
218
219 41
        foreach ($indexes as $index) {
220
            $newIndex = array(
221 41
                'keys' => array(),
222 41
                'options' => $index['options']
223
            );
224 41
            foreach ($index['keys'] as $key => $value) {
225 41
                $key = $persister->prepareFieldName($key);
226 41
                if ($class->hasField($key)) {
227 39
                    $mapping = $class->getFieldMapping($key);
228 39
                    $newIndex['keys'][$mapping['name']] = $value;
229
                } else {
230 41
                    $newIndex['keys'][$key] = $value;
231
                }
232
            }
233
234 41
            $newIndexes[] = $newIndex;
235
        }
236
237 41
        return $newIndexes;
238
    }
239
240
    /**
241
     * Ensure the given document's indexes are created.
242
     *
243
     * @param string $documentName
244
     * @param integer $timeout Timeout (ms) for acknowledged index creation
245
     * @throws \InvalidArgumentException
246
     */
247 37
    public function ensureDocumentIndexes($documentName, $timeout = null)
248
    {
249 37
        $class = $this->dm->getClassMetadata($documentName);
250 37
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isAggregationResultDocument) {
251
            throw new \InvalidArgumentException('Cannot create document indexes for mapped super classes, embedded documents or aggregation result documents.');
252
        }
253 37
        if ($indexes = $this->getDocumentIndexes($documentName)) {
254 37
            $collection = $this->dm->getDocumentCollection($class->name);
255 37
            foreach ($indexes as $index) {
256 37
                $keys = $index['keys'];
257 37
                $options = $index['options'];
258
259 37
                if ( ! isset($options['safe']) && ! isset($options['w'])) {
260 36
                    $options['w'] = 1;
261
                }
262
263 37
                if (isset($options['safe']) && ! isset($options['w'])) {
264 1
                    $options['w'] = is_bool($options['safe']) ? (integer) $options['safe'] : $options['safe'];
265 1
                    unset($options['safe']);
266
                }
267
268 37
                if ( ! isset($options['timeout']) && isset($timeout)) {
269
                    $options['timeout'] = $timeout;
270
                }
271
272 37
                $collection->ensureIndex($keys, $options);
273
            }
274
        }
275 37
    }
276
277
    /**
278
     * Delete indexes for all documents that can be loaded with the
279
     * metadata factory.
280
     */
281 View Code Duplication
    public function deleteIndexes()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
282
    {
283
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
284
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isAggregationResultDocument) {
285
                continue;
286
            }
287
            $this->deleteDocumentIndexes($class->name);
288
        }
289
    }
290
291
    /**
292
     * Delete the given document's indexes.
293
     *
294
     * @param string $documentName
295
     * @throws \InvalidArgumentException
296
     */
297 View Code Duplication
    public function deleteDocumentIndexes($documentName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
298
    {
299
        $class = $this->dm->getClassMetadata($documentName);
300
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isAggregationResultDocument) {
301
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or aggregation result documents.');
302
        }
303
        $this->dm->getDocumentCollection($documentName)->deleteIndexes();
304
    }
305
306
    /**
307
     * Create all the mapped document collections in the metadata factory.
308
     */
309 View Code Duplication
    public function createCollections()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
310
    {
311
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
312
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isAggregationResultDocument) {
313
                continue;
314
            }
315
            $this->createDocumentCollection($class->name);
316
        }
317
    }
318
319
    /**
320
     * Create the document collection for a mapped class.
321
     *
322
     * @param string $documentName
323
     * @throws \InvalidArgumentException
324
     */
325 1
    public function createDocumentCollection($documentName)
326
    {
327 1
        $class = $this->dm->getClassMetadata($documentName);
328
329 1
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isAggregationResultDocument) {
330
            throw new \InvalidArgumentException('Cannot create document collection for mapped super classes, embedded documents or aggregation result documents.');
331
        }
332
333 1
        if ($class->isFile()) {
334
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getCollection() . '.files');
335
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getCollection() . '.chunks');
336
337
            return;
338
        }
339
340 1
        $this->dm->getDocumentDatabase($documentName)->createCollection(
341 1
            $class->getCollection(),
342 1
            $class->getCollectionCapped(),
343 1
            $class->getCollectionSize(),
344 1
            $class->getCollectionMax()
345
        );
346 1
    }
347
348
    /**
349
     * Drop all the mapped document collections in the metadata factory.
350
     */
351 View Code Duplication
    public function dropCollections()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
352
    {
353
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
354
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isAggregationResultDocument) {
355
                continue;
356
            }
357
            $this->dropDocumentCollection($class->name);
358
        }
359
    }
360
361
    /**
362
     * Drop the document collection for a mapped class.
363
     *
364
     * @param string $documentName
365
     * @throws \InvalidArgumentException
366
     */
367 1 View Code Duplication
    public function dropDocumentCollection($documentName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
368
    {
369 1
        $class = $this->dm->getClassMetadata($documentName);
370 1
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isAggregationResultDocument) {
371
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or aggregation result documents.');
372
        }
373 1
        $this->dm->getDocumentDatabase($documentName)->dropCollection(
374 1
            $class->getCollection()
375
        );
376 1
    }
377
378
    /**
379
     * Drop all the mapped document databases in the metadata factory.
380
     */
381 View Code Duplication
    public function dropDatabases()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
382
    {
383
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
384
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isAggregationResultDocument) {
385
                continue;
386
            }
387
            $this->dropDocumentDatabase($class->name);
388
        }
389
    }
390
391
    /**
392
     * Drop the document database for a mapped class.
393
     *
394
     * @param string $documentName
395
     * @throws \InvalidArgumentException
396
     */
397 View Code Duplication
    public function dropDocumentDatabase($documentName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
398
    {
399
        $class = $this->dm->getClassMetadata($documentName);
400
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isAggregationResultDocument) {
401
            throw new \InvalidArgumentException('Cannot drop document database for mapped super classes, embedded documents or aggregation result documents.');
402
        }
403
        $this->dm->getDocumentDatabase($documentName)->drop();
404
    }
405
406
    /**
407
     * Create all the mapped document databases in the metadata factory.
408
     */
409 View Code Duplication
    public function createDatabases()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
410
    {
411
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
412
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isAggregationResultDocument) {
413
                continue;
414
            }
415
            $this->createDocumentDatabase($class->name);
416
        }
417
    }
418
419
    /**
420
     * Create the document database for a mapped class.
421
     *
422
     * @param string $documentName
423
     * @throws \InvalidArgumentException
424
     */
425 View Code Duplication
    public function createDocumentDatabase($documentName)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
426
    {
427
        $class = $this->dm->getClassMetadata($documentName);
428
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isAggregationResultDocument) {
429
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or aggregation result documents.');
430
        }
431
        $this->dm->getDocumentDatabase($documentName)->execute('function() { return true; }');
432
    }
433
434
    /**
435
     * Determine if an index returned by MongoCollection::getIndexInfo() can be
436
     * considered equivalent to an index in class metadata.
437
     *
438
     * Indexes are considered different if:
439
     *
440
     *   (a) Key/direction pairs differ or are not in the same order
441
     *   (b) Sparse or unique options differ
442
     *   (c) Mongo index is unique without dropDups and mapped index is unique
443
     *       with dropDups
444
     *   (d) Geospatial options differ (bits, max, min)
445
     *   (e) The partialFilterExpression differs
446
     *
447
     * Regarding (c), the inverse case is not a reason to delete and
448
     * recreate the index, since dropDups only affects creation of
449
     * the unique index. Additionally, the background option is only
450
     * relevant to index creation and is not considered.
451
     *
452
     * @param array $mongoIndex Mongo index data.
453
     * @param array $documentIndex Document index data.
454
     * @return bool True if the indexes are equivalent, otherwise false.
455
     */
456
    public function isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)
457
    {
458
        $documentIndexOptions = $documentIndex['options'];
459
460
        if ($mongoIndex['key'] != $documentIndex['keys']) {
461
            return false;
462
        }
463
464
        if (empty($mongoIndex['sparse']) xor empty($documentIndexOptions['sparse'])) {
465
            return false;
466
        }
467
468
        if (empty($mongoIndex['unique']) xor empty($documentIndexOptions['unique'])) {
469
            return false;
470
        }
471
472
        if ( ! empty($mongoIndex['unique']) && empty($mongoIndex['dropDups']) &&
473
            ! empty($documentIndexOptions['unique']) && ! empty($documentIndexOptions['dropDups'])) {
474
475
            return false;
476
        }
477
478
        foreach (array('bits', 'max', 'min') as $option) {
479
            if (isset($mongoIndex[$option]) xor isset($documentIndexOptions[$option])) {
480
                return false;
481
            }
482
483
            if (isset($mongoIndex[$option]) && isset($documentIndexOptions[$option]) &&
484
                $mongoIndex[$option] !== $documentIndexOptions[$option]) {
485
486
                return false;
487
            }
488
        }
489
490
        if (empty($mongoIndex['partialFilterExpression']) xor empty($documentIndexOptions['partialFilterExpression'])) {
491
            return false;
492
        }
493
494
        if (isset($mongoIndex['partialFilterExpression']) && isset($documentIndexOptions['partialFilterExpression']) &&
495
            $mongoIndex['partialFilterExpression'] !== $documentIndexOptions['partialFilterExpression']) {
496
497
            return false;
498
        }
499
500
        return true;
501
    }
502
503
    /**
504
     * Ensure collections are sharded for all documents that can be loaded with the
505
     * metadata factory.
506
     *
507
     * @param array $indexOptions Options for `ensureIndex` command. It's performed on an existing collections
508
     *
509
     * @throws MongoDBException
510
     */
511
    public function ensureSharding(array $indexOptions = array())
512
    {
513
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
514
            if ($class->isMappedSuperclass || !$class->isSharded()) {
515
                continue;
516
            }
517
518
            $this->ensureDocumentSharding($class->name, $indexOptions);
519
        }
520
    }
521
522
    /**
523
     * Ensure sharding for collection by document name.
524
     *
525
     * @param string $documentName
526
     * @param array  $indexOptions Options for `ensureIndex` command. It's performed on an existing collections.
527
     *
528
     * @throws MongoDBException
529
     */
530
    public function ensureDocumentSharding($documentName, array $indexOptions = array())
531
    {
532
        $class = $this->dm->getClassMetadata($documentName);
533
        if ( ! $class->isSharded()) {
534
            return;
535
        }
536
537
        $this->enableShardingForDbByDocumentName($documentName);
538
539
        $try = 0;
540
        do {
541
            $result = $this->runShardCollectionCommand($documentName);
542
            $done = true;
543
544
            // Need to check error message because MongoDB 3.0 does not return a code for this error
545
            if ($result['ok'] != 1 && strpos($result['errmsg'], 'please create an index that starts') !== false) {
546
                // The proposed key is not returned when using mongo-php-adapter with ext-mongodb.
547
                // See https://github.com/mongodb/mongo-php-driver/issues/296 for details
548
                if (isset($result['proposedKey'])) {
549
                    $key = $result['proposedKey'];
550
                } else {
551
                    $key = $this->dm->getClassMetadata($documentName)->getShardKey()['keys'];
552
                }
553
554
                $this->dm->getDocumentCollection($documentName)->ensureIndex($key, $indexOptions);
555
                $done = false;
556
                $try++;
557
            }
558
        } while (! $done && $try < 2);
559
560
        // Starting with MongoDB 3.2, this command returns code 20 when a collection is already sharded.
561
        // For older MongoDB versions, check the error message
562 View Code Duplication
        if ($result['ok'] == 1 || (isset($result['code']) && $result['code'] == 20) || $result['errmsg'] == 'already sharded') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
563
            return;
564
        }
565
566
        throw MongoDBException::failedToEnsureDocumentSharding($documentName, $result['errmsg']);
567
    }
568
569
    /**
570
     * Enable sharding for database which contains documents with given name.
571
     *
572
     * @param string $documentName
573
     *
574
     * @throws MongoDBException
575
     */
576
    public function enableShardingForDbByDocumentName($documentName)
577
    {
578
        $dbName = $this->dm->getDocumentDatabase($documentName)->getName();
579
        $adminDb = $this->dm->getConnection()->selectDatabase('admin');
580
        $result = $adminDb->command(array('enableSharding' => $dbName));
581
582
        // Error code is only available with MongoDB 3.2. MongoDB 3.0 only returns a message
583
        // Thus, check code if it exists and fall back on error message
584 View Code Duplication
        if ($result['ok'] == 1 || (isset($result['code']) && $result['code'] == 23) || $result['errmsg'] == 'already enabled') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
585
            return;
586
        }
587
588
        throw MongoDBException::failedToEnableSharding($dbName, $result['errmsg']);
589
    }
590
591
    /**
592
     * @param $documentName
593
     *
594
     * @return array
595
     */
596
    private function runShardCollectionCommand($documentName)
597
    {
598
        $class = $this->dm->getClassMetadata($documentName);
599
        $dbName = $this->dm->getDocumentDatabase($documentName)->getName();
600
        $shardKey = $class->getShardKey();
601
        $adminDb = $this->dm->getConnection()->selectDatabase('admin');
602
603
        $result = $adminDb->command(
604
            array(
605
                'shardCollection' => $dbName . '.' . $class->getCollection(),
606
                'key'             => $shardKey['keys']
607
            )
608
        );
609
610
        return $result;
611
    }
612
}
613