Completed
Push — master ( 73492b...2db3cf )
by Jeremy
11:56
created

SchemaManager   D

Complexity

Total Complexity 138

Size/Duplication

Total Lines 590
Duplicated Lines 17.12 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 85.95%

Importance

Changes 0
Metric Value
wmc 138
lcom 1
cbo 9
dl 101
loc 590
ccs 208
cts 242
cp 0.8595
rs 4.8717
c 0
b 0
f 0

23 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A getDocumentIndexes() 0 5 1
B prepareIndexes() 0 26 4
B updateIndexes() 9 9 5
B createDatabases() 9 9 5
B ensureIndexes() 9 9 5
B deleteIndexes() 9 9 5
B createCollections() 9 9 5
B dropCollections() 9 9 5
C doGetDocumentIndexes() 0 54 16
C ensureDocumentIndexes() 0 29 13
A deleteDocumentIndexes() 8 8 4
B createDocumentCollection() 0 22 5
A dropDocumentCollection() 8 8 4
B dropDatabases() 9 9 5
A dropDocumentDatabase() 8 8 4
D isMongoIndexEquivalentToDocumentIndex() 0 46 17
A ensureSharding() 0 10 4
C ensureDocumentSharding() 3 38 11
B enableShardingForDbByDocumentName() 3 14 5
A runShardCollectionCommand() 0 16 1
A createDocumentDatabase() 8 9 4
D updateDocumentIndexes() 0 45 9

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like SchemaManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SchemaManager, and based on these observations, apply Extract Interface, too.

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 1151
    public function __construct(DocumentManager $dm, ClassMetadataFactory $cmf)
44
    {
45 1151
        $this->dm = $dm;
46 1151
        $this->metadataFactory = $cmf;
47 1151
    }
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 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...
56
    {
57 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
58 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
59 1
                continue;
60
            }
61 1
            $this->ensureDocumentIndexes($class->name, $timeout);
62
        }
63 1
    }
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->isQueryResultDocument) {
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 3
    public function updateDocumentIndexes($documentName, $timeout = null)
94
    {
95 3
        $class = $this->dm->getClassMetadata($documentName);
96
97 3
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
98
            throw new \InvalidArgumentException('Cannot update document indexes for mapped super classes, embedded documents or aggregation result documents.');
99
        }
100
101 3
        $documentIndexes = $this->getDocumentIndexes($documentName);
102 3
        $collection = $this->dm->getDocumentCollection($documentName);
103 3
        $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 3
        $self = $this;
109 3
        $mongoIndexes = array_filter($mongoIndexes, function ($mongoIndex) use ($documentIndexes, $self) {
110 1
            if ('_id_' === $mongoIndex['name']) {
111
                return false;
112
            }
113
114 1
            foreach ($documentIndexes as $documentIndex) {
115 1
                if ($self->isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)) {
116
                    return false;
117
                }
118
            }
119
120 1
            return true;
121 3
        });
122
123
        // Delete indexes that do not exist in class metadata
124 3
        foreach ($mongoIndexes as $mongoIndex) {
125 1
            if (isset($mongoIndex['name'])) {
126
                /* Note: MongoCollection::deleteIndex() cannot delete
127
                 * custom-named indexes, so use the deleteIndexes command.
128
                 */
129 1
                $collection->getDatabase()->command(array(
130 1
                    'deleteIndexes' => $collection->getName(),
131 1
                    'index' => $mongoIndex['name'],
132
                ));
133
            }
134
        }
135
136 3
        $this->ensureDocumentIndexes($documentName, $timeout);
137 3
    }
138
139
    /**
140
     * @param string $documentName
141
     * @return array
142
     */
143 48
    public function getDocumentIndexes($documentName)
144
    {
145 48
        $visited = array();
146 48
        return $this->doGetDocumentIndexes($documentName, $visited);
147
    }
148
149
    /**
150
     * @param string $documentName
151
     * @param array $visited
152
     * @return array
153
     */
154 48
    private function doGetDocumentIndexes($documentName, array &$visited)
155
    {
156 48
        if (isset($visited[$documentName])) {
157 1
            return array();
158
        }
159
160 48
        $visited[$documentName] = true;
161
162 48
        $class = $this->dm->getClassMetadata($documentName);
163 48
        $indexes = $this->prepareIndexes($class);
164 48
        $embeddedDocumentIndexes = array();
165
166
        // Add indexes from embedded & referenced documents
167 48
        foreach ($class->fieldMappings as $fieldMapping) {
168 48
            if (isset($fieldMapping['embedded'])) {
169 31
                if (isset($fieldMapping['targetDocument'])) {
170 31
                    $possibleEmbeds = array($fieldMapping['targetDocument']);
171 2
                } elseif (isset($fieldMapping['discriminatorMap'])) {
172 1
                    $possibleEmbeds = array_unique($fieldMapping['discriminatorMap']);
173
                } else {
174 1
                    continue;
175
                }
176 31
                foreach ($possibleEmbeds as $embed) {
177 31
                    if (isset($embeddedDocumentIndexes[$embed])) {
178 25
                        $embeddedIndexes = $embeddedDocumentIndexes[$embed];
179
                    } else {
180 31
                        $embeddedIndexes = $this->doGetDocumentIndexes($embed, $visited);
181 31
                        $embeddedDocumentIndexes[$embed] = $embeddedIndexes;
182
                    }
183 31
                    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 31
                        $indexes[] = $embeddedIndex;
189
                    }
190
                }
191 48
            } elseif (isset($fieldMapping['reference']) && isset($fieldMapping['targetDocument'])) {
192 31
                foreach ($indexes as $idx => $index) {
193 31
                    $newKeys = array();
194 31
                    foreach ($index['keys'] as $key => $v) {
195 31
                        if ($key == $fieldMapping['name']) {
196 2
                            $key = $fieldMapping['storeAs'] === ClassMetadataInfo::REFERENCE_STORE_AS_ID
197 2
                                ? $key
198 2
                                : $key . '.$id';
199
                        }
200 31
                        $newKeys[$key] = $v;
201
                    }
202 31
                    $indexes[$idx]['keys'] = $newKeys;
203
                }
204
            }
205
        }
206 48
        return $indexes;
207
    }
208
209
    /**
210
     * @param ClassMetadata $class
211
     * @return array
212
     */
213 48
    private function prepareIndexes(ClassMetadata $class)
214
    {
215 48
        $persister = $this->dm->getUnitOfWork()->getDocumentPersister($class->name);
216 48
        $indexes = $class->getIndexes();
217 48
        $newIndexes = array();
218
219 48
        foreach ($indexes as $index) {
220
            $newIndex = array(
221 48
                'keys' => array(),
222 48
                'options' => $index['options']
223
            );
224 48
            foreach ($index['keys'] as $key => $value) {
225 48
                $key = $persister->prepareFieldName($key);
226 48
                if ($class->hasField($key)) {
227 46
                    $mapping = $class->getFieldMapping($key);
228 46
                    $newIndex['keys'][$mapping['name']] = $value;
229
                } else {
230 5
                    $newIndex['keys'][$key] = $value;
231
                }
232
            }
233
234 48
            $newIndexes[] = $newIndex;
235
        }
236
237 48
        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 44
    public function ensureDocumentIndexes($documentName, $timeout = null)
248
    {
249 44
        $class = $this->dm->getClassMetadata($documentName);
250 44
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
251
            throw new \InvalidArgumentException('Cannot create document indexes for mapped super classes, embedded documents or query result documents.');
252
        }
253 44
        if ($indexes = $this->getDocumentIndexes($documentName)) {
254 44
            $collection = $this->dm->getDocumentCollection($class->name);
255 44
            foreach ($indexes as $index) {
256 44
                $keys = $index['keys'];
257 44
                $options = $index['options'];
258
259 44
                if ( ! isset($options['safe']) && ! isset($options['w'])) {
260 43
                    $options['w'] = 1;
261
                }
262
263 44
                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 44
                if ( ! isset($options['timeout']) && isset($timeout)) {
269 1
                    $options['timeout'] = $timeout;
270
                }
271
272 44
                $collection->ensureIndex($keys, $options);
273
            }
274
        }
275 44
    }
276
277
    /**
278
     * Delete indexes for all documents that can be loaded with the
279
     * metadata factory.
280
     */
281 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...
282
    {
283 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
284 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
285 1
                continue;
286
            }
287 1
            $this->deleteDocumentIndexes($class->name);
288
        }
289 1
    }
290
291
    /**
292
     * Delete the given document's indexes.
293
     *
294
     * @param string $documentName
295
     * @throws \InvalidArgumentException
296
     */
297 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...
298
    {
299 2
        $class = $this->dm->getClassMetadata($documentName);
300 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
301
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or query result documents.');
302
        }
303 2
        $this->dm->getDocumentCollection($documentName)->deleteIndexes();
304 2
    }
305
306
    /**
307
     * Create all the mapped document collections in the metadata factory.
308
     */
309 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...
310
    {
311 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
312 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
313 1
                continue;
314
            }
315 1
            $this->createDocumentCollection($class->name);
316
        }
317 1
    }
318
319
    /**
320
     * Create the document collection for a mapped class.
321
     *
322
     * @param string $documentName
323
     * @throws \InvalidArgumentException
324
     */
325 4
    public function createDocumentCollection($documentName)
326
    {
327 4
        $class = $this->dm->getClassMetadata($documentName);
328
329 4
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
330
            throw new \InvalidArgumentException('Cannot create document collection for mapped super classes, embedded documents or query result documents.');
331
        }
332
333 4
        if ($class->isFile()) {
334 2
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getCollection() . '.files');
335 2
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getCollection() . '.chunks');
336
337 2
            return;
338
        }
339
340 3
        $this->dm->getDocumentDatabase($documentName)->createCollection(
341 3
            $class->getCollection(),
342 3
            $class->getCollectionCapped(),
343 3
            $class->getCollectionSize(),
344 3
            $class->getCollectionMax()
345
        );
346 3
    }
347
348
    /**
349
     * Drop all the mapped document collections in the metadata factory.
350
     */
351 2 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 2
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
354 2
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
355 2
                continue;
356
            }
357 2
            $this->dropDocumentCollection($class->name);
358
        }
359 2
    }
360
361
    /**
362
     * Drop the document collection for a mapped class.
363
     *
364
     * @param string $documentName
365
     * @throws \InvalidArgumentException
366
     */
367 4 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 4
        $class = $this->dm->getClassMetadata($documentName);
370 4
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
371
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or query result documents.');
372
        }
373 4
        $this->dm->getDocumentCollection($documentName)->drop();
374 4
    }
375
376
    /**
377
     * Drop all the mapped document databases in the metadata factory.
378
     */
379 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...
380
    {
381 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
382 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
383 1
                continue;
384
            }
385 1
            $this->dropDocumentDatabase($class->name);
386
        }
387 1
    }
388
389
    /**
390
     * Drop the document database for a mapped class.
391
     *
392
     * @param string $documentName
393
     * @throws \InvalidArgumentException
394
     */
395 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...
396
    {
397 2
        $class = $this->dm->getClassMetadata($documentName);
398 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
399
            throw new \InvalidArgumentException('Cannot drop document database for mapped super classes, embedded documents or query result documents.');
400
        }
401 2
        $this->dm->getDocumentDatabase($documentName)->drop();
402 2
    }
403
404
    /**
405
     * Create all the mapped document databases in the metadata factory.
406
     *
407
     * @deprecated Databases are created automatically by MongoDB (>= 3.0). Deprecated since ODM 1.2, to be removed in ODM 2.0.
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->isQueryResultDocument) {
413
                continue;
414
            }
415
            $this->createDocumentDatabase($class->name);
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\ODM\MongoDB\Sch...reateDocumentDatabase() has been deprecated with message: A database is created automatically by MongoDB (>= 3.0). Deprecated since ODM 1.2, to be removed in ODM 2.0.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
416
        }
417
    }
418
419
    /**
420
     * Create the document database for a mapped class.
421
     *
422
     * @param string $documentName
423
     * @throws \InvalidArgumentException
424
     *
425
     * @deprecated A database is created automatically by MongoDB (>= 3.0). Deprecated since ODM 1.2, to be removed in ODM 2.0.
426
     */
427 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...
428
    {
429
        $class = $this->dm->getClassMetadata($documentName);
430
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
431
            throw new \InvalidArgumentException('Cannot create databases for mapped super classes, embedded documents or query result documents.');
432
        }
433
434
        $this->dm->getDocumentDatabase($documentName)->execute('function() { return true; }');
435
    }
436
437
    /**
438
     * Determine if an index returned by MongoCollection::getIndexInfo() can be
439
     * considered equivalent to an index in class metadata.
440
     *
441
     * Indexes are considered different if:
442
     *
443
     *   (a) Key/direction pairs differ or are not in the same order
444
     *   (b) Sparse or unique options differ
445
     *   (c) Mongo index is unique without dropDups and mapped index is unique
446
     *       with dropDups
447
     *   (d) Geospatial options differ (bits, max, min)
448
     *   (e) The partialFilterExpression differs
449
     *
450
     * Regarding (c), the inverse case is not a reason to delete and
451
     * recreate the index, since dropDups only affects creation of
452
     * the unique index. Additionally, the background option is only
453
     * relevant to index creation and is not considered.
454
     *
455
     * @param array $mongoIndex Mongo index data.
456
     * @param array $documentIndex Document index data.
457
     * @return bool True if the indexes are equivalent, otherwise false.
458
     */
459 30
    public function isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)
460
    {
461 30
        $documentIndexOptions = $documentIndex['options'];
462
463 30
        if ($mongoIndex['key'] != $documentIndex['keys']) {
464 2
            return false;
465
        }
466
467 28
        if (empty($mongoIndex['sparse']) xor empty($documentIndexOptions['sparse'])) {
468 2
            return false;
469
        }
470
471 26
        if (empty($mongoIndex['unique']) xor empty($documentIndexOptions['unique'])) {
472 2
            return false;
473
        }
474
475 24
        if ( ! empty($mongoIndex['unique']) && empty($mongoIndex['dropDups']) &&
476 2
            ! empty($documentIndexOptions['unique']) && ! empty($documentIndexOptions['dropDups'])) {
477
478 1
            return false;
479
        }
480
481 23
        foreach (array('bits', 'max', 'min') as $option) {
482 23
            if (isset($mongoIndex[$option]) xor isset($documentIndexOptions[$option])) {
483 6
                return false;
484
            }
485
486 21
            if (isset($mongoIndex[$option]) && isset($documentIndexOptions[$option]) &&
487 6
                $mongoIndex[$option] !== $documentIndexOptions[$option]) {
488
489 3
                return false;
490
            }
491
        }
492
493 14
        if (empty($mongoIndex['partialFilterExpression']) xor empty($documentIndexOptions['partialFilterExpression'])) {
494 2
            return false;
495
        }
496
497 12
        if (isset($mongoIndex['partialFilterExpression']) && isset($documentIndexOptions['partialFilterExpression']) &&
498 2
            $mongoIndex['partialFilterExpression'] !== $documentIndexOptions['partialFilterExpression']) {
499
500 1
            return false;
501
        }
502
503 11
        return true;
504
    }
505
506
    /**
507
     * Ensure collections are sharded for all documents that can be loaded with the
508
     * metadata factory.
509
     *
510
     * @param array $indexOptions Options for `ensureIndex` command. It's performed on an existing collections
511
     *
512
     * @throws MongoDBException
513
     */
514
    public function ensureSharding(array $indexOptions = array())
515
    {
516
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
517
            if ($class->isMappedSuperclass || !$class->isSharded()) {
518
                continue;
519
            }
520
521
            $this->ensureDocumentSharding($class->name, $indexOptions);
522
        }
523
    }
524
525
    /**
526
     * Ensure sharding for collection by document name.
527
     *
528
     * @param string $documentName
529
     * @param array  $indexOptions Options for `ensureIndex` command. It's performed on an existing collections.
530
     *
531
     * @throws MongoDBException
532
     */
533 13
    public function ensureDocumentSharding($documentName, array $indexOptions = array())
534
    {
535 13
        $class = $this->dm->getClassMetadata($documentName);
536 13
        if ( ! $class->isSharded()) {
537
            return;
538
        }
539
540 13
        $this->enableShardingForDbByDocumentName($documentName);
541
542 13
        $try = 0;
543
        do {
544 13
            $result = $this->runShardCollectionCommand($documentName);
545 13
            $done = true;
546
547
            // Need to check error message because MongoDB 3.0 does not return a code for this error
548 13
            if ($result['ok'] != 1 && strpos($result['errmsg'], 'please create an index that starts') !== false) {
549
                // The proposed key is not returned when using mongo-php-adapter with ext-mongodb.
550
                // See https://github.com/mongodb/mongo-php-driver/issues/296 for details
551 2
                if (isset($result['proposedKey'])) {
552
                    $key = $result['proposedKey'];
553
                } else {
554 2
                    $key = $this->dm->getClassMetadata($documentName)->getShardKey()['keys'];
555
                }
556
557 2
                $this->dm->getDocumentCollection($documentName)->ensureIndex($key, $indexOptions);
558 2
                $done = false;
559 2
                $try++;
560
            }
561 13
        } while (! $done && $try < 2);
562
563
        // Starting with MongoDB 3.2, this command returns code 20 when a collection is already sharded.
564
        // For older MongoDB versions, check the error message
565 13 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...
566 12
            return;
567
        }
568
569 1
        throw MongoDBException::failedToEnsureDocumentSharding($documentName, $result['errmsg']);
570
    }
571
572
    /**
573
     * Enable sharding for database which contains documents with given name.
574
     *
575
     * @param string $documentName
576
     *
577
     * @throws MongoDBException
578
     */
579 16
    public function enableShardingForDbByDocumentName($documentName)
580
    {
581 16
        $dbName = $this->dm->getDocumentDatabase($documentName)->getName();
582 16
        $adminDb = $this->dm->getConnection()->selectDatabase('admin');
583 16
        $result = $adminDb->command(array('enableSharding' => $dbName));
584
585
        // Error code is only available with MongoDB 3.2. MongoDB 3.0 only returns a message
586
        // Thus, check code if it exists and fall back on error message
587 16 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...
588 15
            return;
589
        }
590
591 1
        throw MongoDBException::failedToEnableSharding($dbName, $result['errmsg']);
592
    }
593
594
    /**
595
     * @param $documentName
596
     *
597
     * @return array
598
     */
599 13
    private function runShardCollectionCommand($documentName)
600
    {
601 13
        $class = $this->dm->getClassMetadata($documentName);
602 13
        $dbName = $this->dm->getDocumentDatabase($documentName)->getName();
603 13
        $shardKey = $class->getShardKey();
604 13
        $adminDb = $this->dm->getConnection()->selectDatabase('admin');
605
606 13
        $result = $adminDb->command(
607
            array(
608 13
                'shardCollection' => $dbName . '.' . $class->getCollection(),
609 13
                'key'             => $shardKey['keys']
610
            )
611
        );
612
613 13
        return $result;
614
    }
615
}
616