Completed
Push — master ( 7bfeaf...efad7d )
by Andreas
09:38
created

SchemaManager::runShardCollectionCommand()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 9
cts 9
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 10
nc 1
nop 1
crap 1
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 1192
    public function __construct(DocumentManager $dm, ClassMetadataFactory $cmf)
44
    {
45 1192
        $this->dm = $dm;
46 1192
        $this->metadataFactory = $cmf;
47 1192
    }
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 1
                    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 = ClassMetadataInfo::getReferenceFieldName($fieldMapping['storeAs'], $key);
197
                        }
198 31
                        $newKeys[$key] = $v;
199
                    }
200 48
                    $indexes[$idx]['keys'] = $newKeys;
201
                }
202
            }
203
        }
204 48
        return $indexes;
205
    }
206
207
    /**
208
     * @param ClassMetadata $class
209
     * @return array
210
     */
211 48
    private function prepareIndexes(ClassMetadata $class)
212
    {
213 48
        $persister = $this->dm->getUnitOfWork()->getDocumentPersister($class->name);
214 48
        $indexes = $class->getIndexes();
215 48
        $newIndexes = array();
216
217 48
        foreach ($indexes as $index) {
218
            $newIndex = array(
219 48
                'keys' => array(),
220 48
                'options' => $index['options']
221
            );
222 48
            foreach ($index['keys'] as $key => $value) {
223 48
                $key = $persister->prepareFieldName($key);
224 48
                if ($class->hasField($key)) {
225 46
                    $mapping = $class->getFieldMapping($key);
226 46
                    $newIndex['keys'][$mapping['name']] = $value;
227
                } else {
228 48
                    $newIndex['keys'][$key] = $value;
229
                }
230
            }
231
232 48
            $newIndexes[] = $newIndex;
233
        }
234
235 48
        return $newIndexes;
236
    }
237
238
    /**
239
     * Ensure the given document's indexes are created.
240
     *
241
     * @param string $documentName
242
     * @param integer $timeout Timeout (ms) for acknowledged index creation
243
     * @throws \InvalidArgumentException
244
     */
245 44
    public function ensureDocumentIndexes($documentName, $timeout = null)
246
    {
247 44
        $class = $this->dm->getClassMetadata($documentName);
248 44
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
249
            throw new \InvalidArgumentException('Cannot create document indexes for mapped super classes, embedded documents or query result documents.');
250
        }
251 44
        if ($indexes = $this->getDocumentIndexes($documentName)) {
252 44
            $collection = $this->dm->getDocumentCollection($class->name);
253 44
            foreach ($indexes as $index) {
254 44
                $keys = $index['keys'];
255 44
                $options = $index['options'];
256
257 44
                if ( ! isset($options['safe']) && ! isset($options['w'])) {
258 43
                    $options['w'] = 1;
259
                }
260
261 44
                if (isset($options['safe']) && ! isset($options['w'])) {
262 1
                    $options['w'] = is_bool($options['safe']) ? (integer) $options['safe'] : $options['safe'];
263 1
                    unset($options['safe']);
264
                }
265
266 44
                if ( ! isset($options['timeout']) && isset($timeout)) {
267 1
                    $options['timeout'] = $timeout;
268
                }
269
270 44
                $collection->ensureIndex($keys, $options);
271
            }
272
        }
273 44
    }
274
275
    /**
276
     * Delete indexes for all documents that can be loaded with the
277
     * metadata factory.
278
     */
279 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...
280
    {
281 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
282 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
283 1
                continue;
284
            }
285 1
            $this->deleteDocumentIndexes($class->name);
286
        }
287 1
    }
288
289
    /**
290
     * Delete the given document's indexes.
291
     *
292
     * @param string $documentName
293
     * @throws \InvalidArgumentException
294
     */
295 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...
296
    {
297 2
        $class = $this->dm->getClassMetadata($documentName);
298 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
299
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or query result documents.');
300
        }
301 2
        $this->dm->getDocumentCollection($documentName)->deleteIndexes();
302 2
    }
303
304
    /**
305
     * Create all the mapped document collections in the metadata factory.
306
     */
307 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...
308
    {
309 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
310 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
311 1
                continue;
312
            }
313 1
            $this->createDocumentCollection($class->name);
314
        }
315 1
    }
316
317
    /**
318
     * Create the document collection for a mapped class.
319
     *
320
     * @param string $documentName
321
     * @throws \InvalidArgumentException
322
     */
323 4
    public function createDocumentCollection($documentName)
324
    {
325 4
        $class = $this->dm->getClassMetadata($documentName);
326
327 4
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
328
            throw new \InvalidArgumentException('Cannot create document collection for mapped super classes, embedded documents or query result documents.');
329
        }
330
331 4
        if ($class->isFile()) {
332 2
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getCollection() . '.files');
333 2
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getCollection() . '.chunks');
334
335 2
            return;
336
        }
337
338 3
        $this->dm->getDocumentDatabase($documentName)->createCollection(
339 3
            $class->getCollection(),
340 3
            $class->getCollectionCapped(),
341 3
            $class->getCollectionSize(),
342 3
            $class->getCollectionMax()
343
        );
344 3
    }
345
346
    /**
347
     * Drop all the mapped document collections in the metadata factory.
348
     */
349 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...
350
    {
351 2
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
352 2
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
353 2
                continue;
354
            }
355 2
            $this->dropDocumentCollection($class->name);
356
        }
357 2
    }
358
359
    /**
360
     * Drop the document collection for a mapped class.
361
     *
362
     * @param string $documentName
363
     * @throws \InvalidArgumentException
364
     */
365 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...
366
    {
367 4
        $class = $this->dm->getClassMetadata($documentName);
368 4
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
369
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or query result documents.');
370
        }
371 4
        $this->dm->getDocumentCollection($documentName)->drop();
372 4
    }
373
374
    /**
375
     * Drop all the mapped document databases in the metadata factory.
376
     */
377 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...
378
    {
379 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
380 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
381 1
                continue;
382
            }
383 1
            $this->dropDocumentDatabase($class->name);
384
        }
385 1
    }
386
387
    /**
388
     * Drop the document database for a mapped class.
389
     *
390
     * @param string $documentName
391
     * @throws \InvalidArgumentException
392
     */
393 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...
394
    {
395 2
        $class = $this->dm->getClassMetadata($documentName);
396 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
397
            throw new \InvalidArgumentException('Cannot drop document database for mapped super classes, embedded documents or query result documents.');
398
        }
399 2
        $this->dm->getDocumentDatabase($documentName)->drop();
400 2
    }
401
402
    /**
403
     * Create all the mapped document databases in the metadata factory.
404
     *
405
     * @deprecated Databases are created automatically by MongoDB (>= 3.0). Deprecated since ODM 1.2, to be removed in ODM 2.0.
406
     */
407
    public function createDatabases()
408
    {
409
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
410
            sprintf('%s was deprecated in version 1.2 - databases are created automatically by MongoDB (>= 3.0).', __METHOD__),
411
            E_USER_DEPRECATED
412
        );
413
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
414
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
415
                continue;
416
            }
417
            $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...
418
        }
419
    }
420
421
    /**
422
     * Create the document database for a mapped class.
423
     *
424
     * @param string $documentName
425
     * @throws \InvalidArgumentException
426
     *
427
     * @deprecated A database is created automatically by MongoDB (>= 3.0). Deprecated since ODM 1.2, to be removed in ODM 2.0.
428
     */
429
    public function createDocumentDatabase($documentName)
430
    {
431
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

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