Completed
Pull Request — master (#1385)
by Andreas
09:43
created

SchemaManager::ensureDocumentSharding()   C

Complexity

Conditions 13
Paths 7

Size

Total Lines 36
Code Lines 20

Duplication

Lines 3
Ratio 8.33 %

Code Coverage

Tests 17
CRAP Score 13.1973

Importance

Changes 6
Bugs 2 Features 1
Metric Value
c 6
b 2
f 1
dl 3
loc 36
ccs 17
cts 19
cp 0.8947
rs 5.1234
cc 13
eloc 20
nc 7
nop 2
crap 13.1973

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
25
class SchemaManager
26
{
27
    /**
28
     * @var DocumentManager
29
     */
30
    protected $dm;
31
32
    /**
33
     *
34
     * @var ClassMetadataFactory
35
     */
36
    protected $metadataFactory;
37
38
    /**
39
     * @param DocumentManager $dm
40
     * @param ClassMetadataFactory $cmf
41
     */
42 1045
    public function __construct(DocumentManager $dm, ClassMetadataFactory $cmf)
43
    {
44 1045
        $this->dm = $dm;
45 1045
        $this->metadataFactory = $cmf;
46 1045
    }
47
48
    /**
49
     * Ensure indexes are created for all documents that can be loaded with the
50
     * metadata factory.
51
     *
52
     * @param integer $timeout Timeout (ms) for acknowledged index creation
53
     */
54 1 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...
55
    {
56 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
57 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
58 1
                continue;
59
            }
60 1
            $this->ensureDocumentIndexes($class->name, $timeout);
61
        }
62 1
    }
63
64
    /**
65
     * Ensure indexes exist for all mapped document classes.
66
     *
67
     * Indexes that exist in MongoDB but not the document metadata will be
68
     * deleted.
69
     *
70
     * @param integer $timeout Timeout (ms) for acknowledged index creation
71
     */
72 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...
73
    {
74
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
75
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
76
                continue;
77
            }
78
            $this->updateDocumentIndexes($class->name, $timeout);
79
        }
80
    }
81
82
    /**
83
     * Ensure indexes exist for the mapped document class.
84
     *
85
     * Indexes that exist in MongoDB but not the document metadata will be
86
     * deleted.
87
     *
88
     * @param string $documentName
89
     * @param integer $timeout Timeout (ms) for acknowledged index creation
90
     * @throws \InvalidArgumentException
91
     */
92 3
    public function updateDocumentIndexes($documentName, $timeout = null)
93
    {
94 3
        $class = $this->dm->getClassMetadata($documentName);
95
96 3
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
97
            throw new \InvalidArgumentException('Cannot update document indexes for mapped super classes or embedded documents.');
98
        }
99
100 3
        $documentIndexes = $this->getDocumentIndexes($documentName);
101 3
        $collection = $this->dm->getDocumentCollection($documentName);
102 3
        $mongoIndexes = $collection->getIndexInfo();
103
104
        /* Determine which Mongo indexes should be deleted. Exclude the ID index
105
         * and those that are equivalent to any in the class metadata.
106
         */
107 3
        $self = $this;
108 3
        $mongoIndexes = array_filter($mongoIndexes, function ($mongoIndex) use ($documentIndexes, $self) {
109 1
            if ('_id_' === $mongoIndex['name']) {
110
                return false;
111
            }
112
113 1
            foreach ($documentIndexes as $documentIndex) {
114 1
                if ($self->isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)) {
115 1
                    return false;
116
                }
117
            }
118
119 1
            return true;
120 3
        });
121
122
        // Delete indexes that do not exist in class metadata
123 3
        foreach ($mongoIndexes as $mongoIndex) {
124 1
            if (isset($mongoIndex['name'])) {
125
                /* Note: MongoCollection::deleteIndex() cannot delete
126
                 * custom-named indexes, so use the deleteIndexes command.
127
                 */
128 1
                $collection->getDatabase()->command(array(
129 1
                    'deleteIndexes' => $collection->getName(),
130 1
                    'index' => $mongoIndex['name'],
131
                ));
132
            }
133
        }
134
135 3
        $this->ensureDocumentIndexes($documentName, $timeout);
136 3
    }
137
138
    /**
139
     * @param string $documentName
140
     * @return array
141
     */
142 47
    public function getDocumentIndexes($documentName)
143
    {
144 47
        $visited = array();
145 47
        return $this->doGetDocumentIndexes($documentName, $visited);
146
    }
147
148
    /**
149
     * @param string $documentName
150
     * @param array $visited
151
     * @return array
152
     */
153 47
    private function doGetDocumentIndexes($documentName, array &$visited)
154
    {
155 47
        if (isset($visited[$documentName])) {
156 1
            return array();
157
        }
158
159 47
        $visited[$documentName] = true;
160
161 47
        $class = $this->dm->getClassMetadata($documentName);
162 47
        $indexes = $this->prepareIndexes($class);
163 47
        $embeddedDocumentIndexes = array();
164
165
        // Add indexes from embedded & referenced documents
166 47
        foreach ($class->fieldMappings as $fieldMapping) {
167 47
            if (isset($fieldMapping['embedded'])) {
168 31
                if (isset($fieldMapping['targetDocument'])) {
169 31
                    $possibleEmbeds = array($fieldMapping['targetDocument']);
170 2
                } elseif (isset($fieldMapping['discriminatorMap'])) {
171 1
                    $possibleEmbeds = array_unique($fieldMapping['discriminatorMap']);
172
                } else {
173 1
                    continue;
174
                }
175 31
                foreach ($possibleEmbeds as $embed) {
176 31
                    if (isset($embeddedDocumentIndexes[$embed])) {
177 25
                        $embeddedIndexes = $embeddedDocumentIndexes[$embed];
178
                    } else {
179 31
                        $embeddedIndexes = $this->doGetDocumentIndexes($embed, $visited);
180 31
                        $embeddedDocumentIndexes[$embed] = $embeddedIndexes;
181
                    }
182 31
                    foreach ($embeddedIndexes as $embeddedIndex) {
183 25
                        foreach ($embeddedIndex['keys'] as $key => $value) {
184 25
                            $embeddedIndex['keys'][$fieldMapping['name'] . '.' . $key] = $value;
185 25
                            unset($embeddedIndex['keys'][$key]);
186
                        }
187 31
                        $indexes[] = $embeddedIndex;
188
                    }
189
                }
190 47
            } elseif (isset($fieldMapping['reference']) && isset($fieldMapping['targetDocument'])) {
191 31
                foreach ($indexes as $idx => $index) {
192 31
                    $newKeys = array();
193 31
                    foreach ($index['keys'] as $key => $v) {
194 31
                        if ($key == $fieldMapping['name']) {
195 2
                            $key = $fieldMapping['simple'] ? $key : $key . '.$id';
196
                        }
197 31
                        $newKeys[$key] = $v;
198
                    }
199 47
                    $indexes[$idx]['keys'] = $newKeys;
200
                }
201
            }
202
        }
203 47
        return $indexes;
204
    }
205
206
    /**
207
     * @param ClassMetadata $class
208
     * @return array
209
     */
210 47
    private function prepareIndexes(ClassMetadata $class)
211
    {
212 47
        $persister = $this->dm->getUnitOfWork()->getDocumentPersister($class->name);
213 47
        $indexes = $class->getIndexes();
214 47
        $newIndexes = array();
215
216 47
        foreach ($indexes as $index) {
217
            $newIndex = array(
218 47
                'keys' => array(),
219 47
                'options' => $index['options']
220
            );
221 47
            foreach ($index['keys'] as $key => $value) {
222 47
                $key = $persister->prepareFieldName($key);
223 47
                if ($class->hasField($key)) {
224 45
                    $mapping = $class->getFieldMapping($key);
225 45
                    $newIndex['keys'][$mapping['name']] = $value;
226
                } else {
227 47
                    $newIndex['keys'][$key] = $value;
228
                }
229
            }
230
231 47
            $newIndexes[] = $newIndex;
232
        }
233
234 47
        return $newIndexes;
235
    }
236
237
    /**
238
     * Ensure the given document's indexes are created.
239
     *
240
     * @param string $documentName
241
     * @param integer $timeout Timeout (ms) for acknowledged index creation
242
     * @throws \InvalidArgumentException
243
     */
244 43
    public function ensureDocumentIndexes($documentName, $timeout = null)
245
    {
246 43
        $class = $this->dm->getClassMetadata($documentName);
247 43
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
248
            throw new \InvalidArgumentException('Cannot create document indexes for mapped super classes or embedded documents.');
249
        }
250 43
        if ($indexes = $this->getDocumentIndexes($documentName)) {
251 43
            $collection = $this->dm->getDocumentCollection($class->name);
252 43
            foreach ($indexes as $index) {
253 43
                $keys = $index['keys'];
254 43
                $options = $index['options'];
255
256 43
                if ( ! isset($options['safe']) && ! isset($options['w'])) {
257 42
                    $options['w'] = 1;
258
                }
259
260 43
                if (isset($options['safe']) && ! isset($options['w'])) {
261 1
                    $options['w'] = is_bool($options['safe']) ? (integer) $options['safe'] : $options['safe'];
262 1
                    unset($options['safe']);
263
                }
264
265 43
                if ( ! isset($options['timeout']) && isset($timeout)) {
266 1
                    $options['timeout'] = $timeout;
267
                }
268
269 43
                $collection->ensureIndex($keys, $options);
270
            }
271
        }
272 43
    }
273
274
    /**
275
     * Delete indexes for all documents that can be loaded with the
276
     * metadata factory.
277
     */
278 1 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...
279
    {
280 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
281 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
282 1
                continue;
283
            }
284 1
            $this->deleteDocumentIndexes($class->name);
285
        }
286 1
    }
287
288
    /**
289
     * Delete the given document's indexes.
290
     *
291
     * @param string $documentName
292
     * @throws \InvalidArgumentException
293
     */
294 2 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...
295
    {
296 2
        $class = $this->dm->getClassMetadata($documentName);
297 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
298
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes or embedded documents.');
299
        }
300 2
        $this->dm->getDocumentCollection($documentName)->deleteIndexes();
301 2
    }
302
303
    /**
304
     * Create all the mapped document collections in the metadata factory.
305
     */
306 1 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...
307
    {
308 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
309 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
310 1
                continue;
311
            }
312 1
            $this->createDocumentCollection($class->name);
313
        }
314 1
    }
315
316
    /**
317
     * Create the document collection for a mapped class.
318
     *
319
     * @param string $documentName
320
     * @throws \InvalidArgumentException
321
     */
322 4
    public function createDocumentCollection($documentName)
323
    {
324 4
        $class = $this->dm->getClassMetadata($documentName);
325
326 4
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
327
            throw new \InvalidArgumentException('Cannot create document collection for mapped super classes or embedded documents.');
328
        }
329
330 4
        if ($class->isFile()) {
331 2
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getCollection() . '.files');
332 2
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getCollection() . '.chunks');
333
334 2
            return;
335
        }
336
337 3
        $this->dm->getDocumentDatabase($documentName)->createCollection(
338 3
            $class->getCollection(),
339 3
            $class->getCollectionCapped(),
340 3
            $class->getCollectionSize(),
341 3
            $class->getCollectionMax()
342
        );
343 3
    }
344
345
    /**
346
     * Drop all the mapped document collections in the metadata factory.
347
     */
348 1 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...
349
    {
350 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
351 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
352 1
                continue;
353
            }
354 1
            $this->dropDocumentCollection($class->name);
355
        }
356 1
    }
357
358
    /**
359
     * Drop the document collection for a mapped class.
360
     *
361
     * @param string $documentName
362
     * @throws \InvalidArgumentException
363
     */
364 3 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...
365
    {
366 3
        $class = $this->dm->getClassMetadata($documentName);
367 3
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
368
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes or embedded documents.');
369
        }
370 3
        $this->dm->getDocumentDatabase($documentName)->dropCollection(
371 3
            $class->getCollection()
372
        );
373 3
    }
374
375
    /**
376
     * Drop all the mapped document databases in the metadata factory.
377
     */
378 1 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...
379
    {
380 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
381 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
382 1
                continue;
383
            }
384 1
            $this->dropDocumentDatabase($class->name);
385
        }
386 1
    }
387
388
    /**
389
     * Drop the document database for a mapped class.
390
     *
391
     * @param string $documentName
392
     * @throws \InvalidArgumentException
393
     */
394 2 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...
395
    {
396 2
        $class = $this->dm->getClassMetadata($documentName);
397 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
398
            throw new \InvalidArgumentException('Cannot drop document database for mapped super classes or embedded documents.');
399
        }
400 2
        $this->dm->getDocumentDatabase($documentName)->drop();
401 2
    }
402
403
    /**
404
     * Create all the mapped document databases in the metadata factory.
405
     */
406 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...
407
    {
408
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
409
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
410
                continue;
411
            }
412
            $this->createDocumentDatabase($class->name);
413
        }
414
    }
415
416
    /**
417
     * Create the document database for a mapped class.
418
     *
419
     * @param string $documentName
420
     * @throws \InvalidArgumentException
421
     */
422 1 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...
423
    {
424 1
        $class = $this->dm->getClassMetadata($documentName);
425 1
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument) {
426
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes or embedded documents.');
427
        }
428 1
        $this->dm->getDocumentDatabase($documentName)->execute('function() { return true; }');
429 1
    }
430
431
    /**
432
     * Determine if an index returned by MongoCollection::getIndexInfo() can be
433
     * considered equivalent to an index in class metadata.
434
     *
435
     * Indexes are considered different if:
436
     *
437
     *   (a) Key/direction pairs differ or are not in the same order
438
     *   (b) Sparse or unique options differ
439
     *   (c) Mongo index is unique without dropDups and mapped index is unique
440
     *       with dropDups
441
     *   (d) Geospatial options differ (bits, max, min)
442
     *   (e) The partialFilterExpression differs
443
     *
444
     * Regarding (c), the inverse case is not a reason to delete and
445
     * recreate the index, since dropDups only affects creation of
446
     * the unique index. Additionally, the background option is only
447
     * relevant to index creation and is not considered.
448
     *
449
     * @param array $mongoIndex Mongo index data.
450
     * @param array $documentIndex Document index data.
451
     * @return bool True if the indexes are equivalent, otherwise false.
452
     */
453 30
    public function isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)
454
    {
455 30
        $documentIndexOptions = $documentIndex['options'];
456
457 30
        if ($mongoIndex['key'] != $documentIndex['keys']) {
458 2
            return false;
459
        }
460
461 28
        if (empty($mongoIndex['sparse']) xor empty($documentIndexOptions['sparse'])) {
462 2
            return false;
463
        }
464
465 26
        if (empty($mongoIndex['unique']) xor empty($documentIndexOptions['unique'])) {
466 2
            return false;
467
        }
468
469 24
        if ( ! empty($mongoIndex['unique']) && empty($mongoIndex['dropDups']) &&
470 24
            ! empty($documentIndexOptions['unique']) && ! empty($documentIndexOptions['dropDups'])) {
471
472 1
            return false;
473
        }
474
475 23
        foreach (array('bits', 'max', 'min') as $option) {
476 23
            if (isset($mongoIndex[$option]) xor isset($documentIndexOptions[$option])) {
477 6
                return false;
478
            }
479
480 21
            if (isset($mongoIndex[$option]) && isset($documentIndexOptions[$option]) &&
481 21
                $mongoIndex[$option] !== $documentIndexOptions[$option]) {
482
483 21
                return false;
484
            }
485
        }
486
487 14
        if (empty($mongoIndex['partialFilterExpression']) xor empty($documentIndexOptions['partialFilterExpression'])) {
488 2
            return false;
489
        }
490
491 12
        if (isset($mongoIndex['partialFilterExpression']) && isset($documentIndexOptions['partialFilterExpression']) &&
492 12
            $mongoIndex['partialFilterExpression'] !== $documentIndexOptions['partialFilterExpression']) {
493
494 1
            return false;
495
        }
496
497 11
        return true;
498
    }
499
500
    /**
501
     * Ensure collections are sharded for all documents that can be loaded with the
502
     * metadata factory.
503
     *
504
     * @param array $indexOptions Options for `ensureIndex` command. It's performed on an existing collections
505
     *
506
     * @throws MongoDBException
507
     */
508
    public function ensureSharding(array $indexOptions = array())
509
    {
510
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
511
            if ($class->isMappedSuperclass || !$class->isSharded()) {
512
                continue;
513
            }
514
515
            $this->ensureDocumentSharding($class->name, $indexOptions);
516
        }
517
    }
518
519
    /**
520
     * Ensure sharding for collection by document name.
521
     *
522
     * @param string $documentName
523
     * @param array  $indexOptions Options for `ensureIndex` command. It's performed on an existing collections.
524
     *
525
     * @throws MongoDBException
526
     */
527 12
    public function ensureDocumentSharding($documentName, array $indexOptions = array())
528
    {
529 12
        $class = $this->dm->getClassMetadata($documentName);
530 12
        if ( ! $class->isSharded()) {
531
            return;
532
        }
533
534 12
        $this->enableShardingForDbByDocumentName($documentName);
535
536 12
        $try = 0;
537
        do {
538 12
            $result = $this->runShardCollectionCommand($documentName);
539 12
            $done = true;
540
541 12
            if ($result['ok'] != 1 && ((isset($result['code']) && $result['code'] == 59) || isset($result['proposedKey']))) {
542
                // Code 59: need to create an index for shard key. See if we've got a proposed key
543
                // The proposed key is not returned when using mongo-php-adapter with ext-mongodb.
544
                // See https://github.com/mongodb/mongo-php-driver/issues/296 for details
545 2
                if (isset($result['proposedKey'])) {
546
                    $key = $result['proposedKey'];
547
                } else {
548 2
                    $key = $this->dm->getClassMetadata($documentName)->getShardKey()['keys'];
549
                }
550
551 2
                $this->dm->getDocumentCollection($documentName)->ensureIndex($key, $indexOptions);
552 2
                $done = false;
553 2
                $try++;
554
            }
555 12
        } while (! $done && $try < 2);
556
557
        // Different MongoDB versions return different result sets.
558
        // Thus, check code if it exists and fall back on error message
559 12 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...
560 1
            throw MongoDBException::failedToEnsureDocumentSharding($documentName, $result['errmsg']);
561
        }
562 11
    }
563
564
    /**
565
     * Enable sharding for database which contains documents with given name.
566
     *
567
     * @param string $documentName
568
     *
569
     * @throws MongoDBException
570
     */
571 15
    public function enableShardingForDbByDocumentName($documentName)
572
    {
573 15
        $dbName = $this->dm->getDocumentDatabase($documentName)->getName();
574 15
        $adminDb = $this->dm->getConnection()->selectDatabase('admin');
575 15
        $result = $adminDb->command(array('enableSharding' => $dbName));
576
577
        // Different MongoDB versions return different result sets.
578
        // Thus, check code if it exists and fall back on error message
579 15 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...
580 1
            throw MongoDBException::failedToEnableSharding($dbName, $result['errmsg']);
581
        }
582 14
    }
583
584
    /**
585
     * @param $documentName
586
     *
587
     * @return array
588
     */
589 12
    private function runShardCollectionCommand($documentName)
590
    {
591 12
        $class = $this->dm->getClassMetadata($documentName);
592 12
        $dbName = $this->dm->getDocumentDatabase($documentName)->getName();
593 12
        $shardKey = $class->getShardKey();
594 12
        $adminDb = $this->dm->getConnection()->selectDatabase('admin');
595
596 12
        $result = $adminDb->command(
597
            array(
598 12
                'shardCollection' => $dbName . '.' . $class->getCollection(),
599 12
                'key'             => $shardKey['keys']
600
            ),
601 12
            $shardKey['options']
602
        );
603
604 12
        return $result;
605
    }
606
}
607