Completed
Pull Request — master (#1860)
by Andreas
14:44
created

SchemaManager::dropDatabases()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 9.6111
c 0
b 0
f 0
cc 5
nc 3
nop 0
crap 5
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ODM\MongoDB;
6
7
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
8
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactory;
9
use InvalidArgumentException;
10
use MongoDB\Driver\Exception\RuntimeException;
11
use MongoDB\Model\IndexInfo;
12
use function array_filter;
13
use function array_unique;
14
use function iterator_to_array;
15
use function ksort;
16
use function strpos;
17
18
class SchemaManager
19
{
20
    private const GRIDFS_FILE_COLLECTION_INDEX = ['files_id' => 1, 'n' => 1];
21
22
    private const GRIDFS_CHUNKS_COLLECTION_INDEX = ['filename' => 1, 'uploadDate' => 1];
23
24
    /** @var DocumentManager */
25
    protected $dm;
26
27
    /** @var ClassMetadataFactory */
28
    protected $metadataFactory;
29
30 1665
    public function __construct(DocumentManager $dm, ClassMetadataFactory $cmf)
31
    {
32 1665
        $this->dm = $dm;
33 1665
        $this->metadataFactory = $cmf;
34 1665
    }
35
36
    /**
37
     * Ensure indexes are created for all documents that can be loaded with the
38
     * metadata factory.
39
     */
40 1
    public function ensureIndexes(?int $timeout = null) : void
41
    {
42 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
43 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
0 ignored issues
show
Bug introduced by
Accessing isMappedSuperclass on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing isEmbeddedDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing isQueryResultDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
44 1
                continue;
45
            }
46
47 1
            $this->ensureDocumentIndexes($class->name, $timeout);
0 ignored issues
show
Bug introduced by
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
48
        }
49 1
    }
50
51
    /**
52
     * Ensure indexes exist for all mapped document classes.
53
     *
54
     * Indexes that exist in MongoDB but not the document metadata will be
55
     * deleted.
56
     */
57
    public function updateIndexes(?int $timeout = null) : void
58
    {
59
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
60
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
0 ignored issues
show
Bug introduced by
Accessing isMappedSuperclass on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing isEmbeddedDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing isQueryResultDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
61
                continue;
62
            }
63
64
            $this->updateDocumentIndexes($class->name, $timeout);
0 ignored issues
show
Bug introduced by
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
65
        }
66
    }
67
68
    /**
69
     * Ensure indexes exist for the mapped document class.
70
     *
71
     * Indexes that exist in MongoDB but not the document metadata will be
72
     * deleted.
73
     *
74
     * @throws InvalidArgumentException
75
     */
76 3
    public function updateDocumentIndexes(string $documentName, ?int $timeout = null) : void
77
    {
78 3
        $class = $this->dm->getClassMetadata($documentName);
79
80 3
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
81
            throw new InvalidArgumentException('Cannot update document indexes for mapped super classes, embedded documents or aggregation result documents.');
82
        }
83
84 3
        $documentIndexes = $this->getDocumentIndexes($documentName);
85 3
        $collection = $this->dm->getDocumentCollection($documentName);
86 3
        $mongoIndexes = iterator_to_array($collection->listIndexes());
87
88
        /* Determine which Mongo indexes should be deleted. Exclude the ID index
89
         * and those that are equivalent to any in the class metadata.
90
         */
91 3
        $self = $this;
92
        $mongoIndexes = array_filter($mongoIndexes, static function (IndexInfo $mongoIndex) use ($documentIndexes, $self) {
93 1
            if ($mongoIndex['name'] === '_id_') {
94
                return false;
95
            }
96
97 1
            foreach ($documentIndexes as $documentIndex) {
98 1
                if ($self->isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)) {
99 1
                    return false;
100
                }
101
            }
102
103 1
            return true;
104 3
        });
105
106
        // Delete indexes that do not exist in class metadata
107 3
        foreach ($mongoIndexes as $mongoIndex) {
108 1
            if (! isset($mongoIndex['name'])) {
109
                continue;
110
            }
111
112 1
            $collection->dropIndex($mongoIndex['name']);
113
        }
114
115 3
        $this->ensureDocumentIndexes($documentName, $timeout);
116 3
    }
117
118 20
    public function getDocumentIndexes(string $documentName) : array
119
    {
120 20
        $visited = [];
121 20
        return $this->doGetDocumentIndexes($documentName, $visited);
122
    }
123
124 20
    private function doGetDocumentIndexes(string $documentName, array &$visited) : array
125
    {
126 20
        if (isset($visited[$documentName])) {
127 1
            return [];
128
        }
129
130 20
        $visited[$documentName] = true;
131
132 20
        $class = $this->dm->getClassMetadata($documentName);
133 20
        $indexes = $this->prepareIndexes($class);
134 20
        $embeddedDocumentIndexes = [];
135
136
        // Add indexes from embedded & referenced documents
137 20
        foreach ($class->fieldMappings as $fieldMapping) {
138 20
            if (isset($fieldMapping['embedded'])) {
139 4
                if (isset($fieldMapping['targetDocument'])) {
140 4
                    $possibleEmbeds = [$fieldMapping['targetDocument']];
141 2
                } elseif (isset($fieldMapping['discriminatorMap'])) {
142 1
                    $possibleEmbeds = array_unique($fieldMapping['discriminatorMap']);
143
                } else {
144 1
                    continue;
145
                }
146
147 4
                foreach ($possibleEmbeds as $embed) {
148 4
                    if (isset($embeddedDocumentIndexes[$embed])) {
149 2
                        $embeddedIndexes = $embeddedDocumentIndexes[$embed];
150
                    } else {
151 4
                        $embeddedIndexes = $this->doGetDocumentIndexes($embed, $visited);
152 4
                        $embeddedDocumentIndexes[$embed] = $embeddedIndexes;
153
                    }
154
155 4
                    foreach ($embeddedIndexes as $embeddedIndex) {
156 2
                        foreach ($embeddedIndex['keys'] as $key => $value) {
157 2
                            $embeddedIndex['keys'][$fieldMapping['name'] . '.' . $key] = $value;
158 2
                            unset($embeddedIndex['keys'][$key]);
159
                        }
160
161 4
                        $indexes[] = $embeddedIndex;
162
                    }
163
                }
164 20
            } elseif (isset($fieldMapping['reference']) && isset($fieldMapping['targetDocument'])) {
165 9
                foreach ($indexes as $idx => $index) {
166 8
                    $newKeys = [];
167 8
                    foreach ($index['keys'] as $key => $v) {
168 8
                        if ($key === $fieldMapping['name']) {
169 2
                            $key = ClassMetadata::getReferenceFieldName($fieldMapping['storeAs'], $key);
170
                        }
171
172 8
                        $newKeys[$key] = $v;
173
                    }
174
175 20
                    $indexes[$idx]['keys'] = $newKeys;
176
                }
177
            }
178
        }
179
180 20
        return $indexes;
181
    }
182
183 20
    private function prepareIndexes(ClassMetadata $class) : array
184
    {
185 20
        $persister = $this->dm->getUnitOfWork()->getDocumentPersister($class->name);
186 20
        $indexes = $class->getIndexes();
187 20
        $newIndexes = [];
188
189 20
        foreach ($indexes as $index) {
190
            $newIndex = [
191 19
                'keys' => [],
192 19
                'options' => $index['options'],
193
            ];
194
195 19
            foreach ($index['keys'] as $key => $value) {
196 19
                $key = $persister->prepareFieldName($key);
197 19
                if ($class->hasField($key)) {
198 17
                    $mapping = $class->getFieldMapping($key);
199 17
                    $newIndex['keys'][$mapping['name']] = $value;
200
                } else {
201 19
                    $newIndex['keys'][$key] = $value;
202
                }
203
            }
204
205 19
            $newIndexes[] = $newIndex;
206
        }
207
208 20
        return $newIndexes;
209
    }
210
211
    /**
212
     * Ensure the given document's indexes are created.
213
     *
214
     * @throws InvalidArgumentException
215
     */
216 16
    public function ensureDocumentIndexes(string $documentName, ?int $timeoutMs = null) : void
217
    {
218 16
        $class = $this->dm->getClassMetadata($documentName);
219 16
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
220
            throw new InvalidArgumentException('Cannot create document indexes for mapped super classes, embedded documents or query result documents.');
221
        }
222
223 16
        if ($class->isFile) {
224 2
            $this->ensureGridFSIndexes($class);
225
        }
226
227 16
        $indexes = $this->getDocumentIndexes($documentName);
228 16
        if (! $indexes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $indexes of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
229 4
            return;
230
        }
231
232 15
        $collection = $this->dm->getDocumentCollection($class->name);
233 15
        foreach ($indexes as $index) {
234 15
            $keys = $index['keys'];
235 15
            $options = $index['options'];
236
237 15
            if (! isset($options['timeout']) && isset($timeoutMs)) {
238 1
                $options['timeout'] = $timeoutMs;
239
            }
240
241 15
            $collection->createIndex($keys, $options);
242
        }
243 15
    }
244
245
    /**
246
     * Delete indexes for all documents that can be loaded with the
247
     * metadata factory.
248
     */
249 1
    public function deleteIndexes() : void
250
    {
251 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
252 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
0 ignored issues
show
Bug introduced by
Accessing isMappedSuperclass on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing isEmbeddedDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing isQueryResultDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
253 1
                continue;
254
            }
255
256 1
            $this->deleteDocumentIndexes($class->name);
0 ignored issues
show
Bug introduced by
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
257
        }
258 1
    }
259
260
    /**
261
     * Delete the given document's indexes.
262
     *
263
     * @throws InvalidArgumentException
264
     */
265 2
    public function deleteDocumentIndexes(string $documentName) : void
266
    {
267 2
        $class = $this->dm->getClassMetadata($documentName);
268 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
269
            throw new InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or query result documents.');
270
        }
271
272 2
        $this->dm->getDocumentCollection($documentName)->dropIndexes();
273 2
    }
274
275
    /**
276
     * Create all the mapped document collections in the metadata factory.
277
     */
278 1
    public function createCollections() : void
279
    {
280 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
281 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
0 ignored issues
show
Bug introduced by
Accessing isMappedSuperclass on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing isEmbeddedDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing isQueryResultDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
282 1
                continue;
283
            }
284 1
            $this->createDocumentCollection($class->name);
0 ignored issues
show
Bug introduced by
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
285
        }
286 1
    }
287
288
    /**
289
     * Create the document collection for a mapped class.
290
     *
291
     * @throws InvalidArgumentException
292
     */
293 5
    public function createDocumentCollection(string $documentName) : void
294
    {
295 5
        $class = $this->dm->getClassMetadata($documentName);
296
297 5
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
298
            throw new InvalidArgumentException('Cannot create document collection for mapped super classes, embedded documents or query result documents.');
299
        }
300
301 5
        if ($class->isFile) {
302 2
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getBucketName() . '.files');
303 2
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getBucketName() . '.chunks');
304
305 2
            return;
306
        }
307
308 4
        $this->dm->getDocumentDatabase($documentName)->createCollection(
309 4
            $class->getCollection(),
310
            [
311 4
                'capped' => $class->getCollectionCapped(),
312 4
                'size' => $class->getCollectionSize(),
313 4
                'max' => $class->getCollectionMax(),
314
            ]
315
        );
316 4
    }
317
318
    /**
319
     * Drop all the mapped document collections in the metadata factory.
320
     */
321 1
    public function dropCollections() : void
322
    {
323 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
324 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
0 ignored issues
show
Bug introduced by
Accessing isMappedSuperclass on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing isEmbeddedDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing isQueryResultDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
325 1
                continue;
326
            }
327
328 1
            $this->dropDocumentCollection($class->name);
0 ignored issues
show
Bug introduced by
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
329
        }
330 1
    }
331
332
    /**
333
     * Drop the document collection for a mapped class.
334
     *
335
     * @throws InvalidArgumentException
336
     */
337 5
    public function dropDocumentCollection(string $documentName) : void
338
    {
339 5
        $class = $this->dm->getClassMetadata($documentName);
340 5
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
341
            throw new InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or query result documents.');
342
        }
343
344 5
        $this->dm->getDocumentCollection($documentName)->drop();
345
346 5
        if (! $class->isFile) {
347 4
            return;
348
        }
349
350 2
        $this->dm->getDocumentBucket($documentName)->getChunksCollection()->drop();
351 2
    }
352
353
    /**
354
     * Drop all the mapped document databases in the metadata factory.
355
     */
356 1
    public function dropDatabases() : void
357
    {
358 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
359 1
            if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
0 ignored issues
show
Bug introduced by
Accessing isMappedSuperclass on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing isEmbeddedDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
Bug introduced by
Accessing isQueryResultDocument on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
360 1
                continue;
361
            }
362
363 1
            $this->dropDocumentDatabase($class->name);
0 ignored issues
show
Bug introduced by
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
364
        }
365 1
    }
366
367
    /**
368
     * Drop the document database for a mapped class.
369
     *
370
     * @throws InvalidArgumentException
371
     */
372 2
    public function dropDocumentDatabase(string $documentName) : void
373
    {
374 2
        $class = $this->dm->getClassMetadata($documentName);
375 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
376
            throw new InvalidArgumentException('Cannot drop document database for mapped super classes, embedded documents or query result documents.');
377
        }
378
379 2
        $this->dm->getDocumentDatabase($documentName)->drop();
380 2
    }
381
382
    /**
383
     * Determine if an index returned by MongoCollection::getIndexInfo() can be
384
     * considered equivalent to an index in class metadata.
385
     *
386
     * Indexes are considered different if:
387
     *
388
     *   (a) Key/direction pairs differ or are not in the same order
389
     *   (b) Sparse or unique options differ
390
     *   (c) Mongo index is unique without dropDups and mapped index is unique
391
     *       with dropDups
392
     *   (d) Geospatial options differ (bits, max, min)
393
     *   (e) The partialFilterExpression differs
394
     *
395
     * Regarding (c), the inverse case is not a reason to delete and
396
     * recreate the index, since dropDups only affects creation of
397
     * the unique index. Additionally, the background option is only
398
     * relevant to index creation and is not considered.
399
     *
400
     * @param array|IndexInfo $mongoIndex Mongo index data.
401
     */
402 47
    public function isMongoIndexEquivalentToDocumentIndex($mongoIndex, array $documentIndex) : bool
403
    {
404 47
        $documentIndexOptions = $documentIndex['options'];
405
406 47
        if (! $this->isEquivalentIndexKeys($mongoIndex, $documentIndex)) {
407 4
            return false;
408
        }
409
410 43
        if (empty($mongoIndex['sparse']) xor empty($documentIndexOptions['sparse'])) {
411 2
            return false;
412
        }
413
414 41
        if (empty($mongoIndex['unique']) xor empty($documentIndexOptions['unique'])) {
415 2
            return false;
416
        }
417
418 39
        if (! empty($mongoIndex['unique']) && empty($mongoIndex['dropDups']) &&
419 39
            ! empty($documentIndexOptions['unique']) && ! empty($documentIndexOptions['dropDups'])) {
420 1
            return false;
421
        }
422
423 38
        foreach (['bits', 'max', 'min'] as $option) {
424 38
            if (isset($mongoIndex[$option]) xor isset($documentIndexOptions[$option])) {
425 6
                return false;
426
            }
427
428 36
            if (isset($mongoIndex[$option], $documentIndexOptions[$option]) &&
429 36
                $mongoIndex[$option] !== $documentIndexOptions[$option]) {
430 36
                return false;
431
            }
432
        }
433
434 29
        if (empty($mongoIndex['partialFilterExpression']) xor empty($documentIndexOptions['partialFilterExpression'])) {
435 2
            return false;
436
        }
437
438 27
        if (isset($mongoIndex['partialFilterExpression'], $documentIndexOptions['partialFilterExpression']) &&
439 27
            $mongoIndex['partialFilterExpression'] !== $documentIndexOptions['partialFilterExpression']) {
440 1
            return false;
441
        }
442
443 26
        if (isset($mongoIndex['weights']) && ! $this->isEquivalentTextIndexWeights($mongoIndex, $documentIndex)) {
444 2
            return false;
445
        }
446
447 24
        foreach (['default_language', 'language_override', 'textIndexVersion'] as $option) {
448
            /* Text indexes will always report defaults for these options, so
449
             * only compare if we have explicit values in the document index. */
450 24
            if (isset($mongoIndex[$option], $documentIndexOptions[$option]) &&
451 24
                $mongoIndex[$option] !== $documentIndexOptions[$option]) {
452 24
                return false;
453
            }
454
        }
455
456 21
        return true;
457
    }
458
459
    /**
460
     * Determine if the keys for a MongoDB index can be considered equivalent to
461
     * those for an index in class metadata.
462
     *
463
     * @param array|IndexInfo $mongoIndex Mongo index data.
464
     */
465 47
    private function isEquivalentIndexKeys($mongoIndex, array $documentIndex) : bool
466
    {
467 47
        $mongoIndexKeys    = $mongoIndex['key'];
468 47
        $documentIndexKeys = $documentIndex['keys'];
469
470
        /* If we are dealing with text indexes, we need to unset internal fields
471
         * from the MongoDB index and filter out text fields from the document
472
         * index. This will leave only non-text fields, which we can compare as
473
         * normal. Any text fields in the document index will be compared later
474
         * with isEquivalentTextIndexWeights(). */
475 47
        if (isset($mongoIndexKeys['_fts']) && $mongoIndexKeys['_fts'] === 'text') {
476 15
            unset($mongoIndexKeys['_fts'], $mongoIndexKeys['_ftsx']);
477
478
            $documentIndexKeys = array_filter($documentIndexKeys, static function ($type) {
479 15
                return $type !== 'text';
480 15
            });
481
        }
482
483
        /* Avoid a strict equality check here. The numeric type returned by
484
         * MongoDB may differ from the document index without implying that the
485
         * indexes themselves are inequivalent. */
486
        // phpcs:disable SlevomatCodingStandard.ControlStructures.DisallowEqualOperators.DisallowedEqualOperator
487 47
        return $mongoIndexKeys == $documentIndexKeys;
488
    }
489
490
    /**
491
     * Determine if the text index weights for a MongoDB index can be considered
492
     * equivalent to those for an index in class metadata.
493
     *
494
     * @param array|IndexInfo $mongoIndex Mongo index data.
495
     */
496 14
    private function isEquivalentTextIndexWeights($mongoIndex, array $documentIndex) : bool
497
    {
498 14
        $mongoIndexWeights    = $mongoIndex['weights'];
499 14
        $documentIndexWeights = $documentIndex['options']['weights'] ?? [];
500
501
        // If not specified, assign a default weight for text fields
502 14
        foreach ($documentIndex['keys'] as $key => $type) {
503 14
            if ($type !== 'text' || isset($documentIndexWeights[$key])) {
504 5
                continue;
505
            }
506
507 9
            $documentIndexWeights[$key] = 1;
508
        }
509
510
        /* MongoDB returns the weights sorted by field name, but we'll sort both
511
         * arrays in case that is internal behavior not to be relied upon. */
512 14
        ksort($mongoIndexWeights);
513 14
        ksort($documentIndexWeights);
514
515
        /* Avoid a strict equality check here. The numeric type returned by
516
         * MongoDB may differ from the document index without implying that the
517
         * indexes themselves are inequivalent. */
518
        // phpcs:disable SlevomatCodingStandard.ControlStructures.DisallowEqualOperators.DisallowedEqualOperator
519 14
        return $mongoIndexWeights == $documentIndexWeights;
520
    }
521
522
    /**
523
     * Ensure collections are sharded for all documents that can be loaded with the
524
     * metadata factory.
525
     *
526
     * @param array $indexOptions Options for `ensureIndex` command. It's performed on an existing collections
527
     *
528
     * @throws MongoDBException
529
     */
530
    public function ensureSharding(array $indexOptions = []) : void
531
    {
532
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
533
            if ($class->isMappedSuperclass || ! $class->isSharded()) {
0 ignored issues
show
Bug introduced by
Accessing isMappedSuperclass on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
534
                continue;
535
            }
536
537
            $this->ensureDocumentSharding($class->name, $indexOptions);
0 ignored issues
show
Bug introduced by
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
538
        }
539
    }
540
541
    /**
542
     * Ensure sharding for collection by document name.
543
     *
544
     * @param array $indexOptions Options for `ensureIndex` command. It's performed on an existing collections.
545
     *
546
     * @throws MongoDBException
547
     */
548 2
    public function ensureDocumentSharding(string $documentName, array $indexOptions = []) : void
549
    {
550 2
        $class = $this->dm->getClassMetadata($documentName);
551 2
        if (! $class->isSharded()) {
552
            return;
553
        }
554
555 2
        $this->enableShardingForDbByDocumentName($documentName);
556
557 2
        $try = 0;
558
        do {
559
            try {
560 2
                $result = $this->runShardCollectionCommand($documentName);
561 2
                $done = true;
562
563
                // Need to check error message because MongoDB 3.0 does not return a code for this error
564 2
                if (! (bool) $result['ok'] && strpos($result['errmsg'], 'please create an index that starts') !== false) {
565
                    // The proposed key is not returned when using mongo-php-adapter with ext-mongodb.
566
                    // See https://github.com/mongodb/mongo-php-driver/issues/296 for details
567
                    $key = $result['proposedKey'] ?? $this->dm->getClassMetadata($documentName)->getShardKey()['keys'];
568
569
                    $this->dm->getDocumentCollection($documentName)->ensureIndex($key, $indexOptions);
0 ignored issues
show
Bug introduced by
The method ensureIndex() does not seem to exist on object<MongoDB\Collection>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
570 2
                    $done = false;
571
                }
572 1
            } catch (RuntimeException $e) {
0 ignored issues
show
Bug introduced by
The class MongoDB\Driver\Exception\RuntimeException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
573 1
                if ($e->getCode() === 20 || $e->getCode() === 23 || $e->getMessage() === 'already sharded') {
574 1
                    return;
575
                }
576
577
                throw $e;
578
            }
579 2
        } while (! $done && $try < 2);
0 ignored issues
show
introduced by
It seems like the condition $try < 2 is always satisfied by any possible value of $try. Are you sure you do not have a deadlock here?
Loading history...
580
581
        // Starting with MongoDB 3.2, this command returns code 20 when a collection is already sharded.
582
        // For older MongoDB versions, check the error message
583 2
        if ((bool) $result['ok'] || (isset($result['code']) && $result['code'] === 20) || $result['errmsg'] === 'already sharded') {
584 2
            return;
585
        }
586
587
        throw MongoDBException::failedToEnsureDocumentSharding($documentName, $result['errmsg']);
588
    }
589
590
    /**
591
     * Enable sharding for database which contains documents with given name.
592
     *
593
     * @throws MongoDBException
594
     */
595 2
    public function enableShardingForDbByDocumentName(string $documentName) : void
596
    {
597 2
        $dbName = $this->dm->getDocumentDatabase($documentName)->getDatabaseName();
598 2
        $adminDb = $this->dm->getClient()->selectDatabase('admin');
599
600
        try {
601 2
            $adminDb->command(['enableSharding' => $dbName]);
602 1
        } catch (RuntimeException $e) {
0 ignored issues
show
Bug introduced by
The class MongoDB\Driver\Exception\RuntimeException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
603 1
            if ($e->getCode() !== 23 || $e->getMessage() === 'already enabled') {
604
                throw MongoDBException::failedToEnableSharding($dbName, $e->getMessage());
605
            }
606
        }
607 2
    }
608
609 2
    private function runShardCollectionCommand(string $documentName) : array
610
    {
611 2
        $class = $this->dm->getClassMetadata($documentName);
612 2
        $dbName = $this->dm->getDocumentDatabase($documentName)->getDatabaseName();
613 2
        $shardKey = $class->getShardKey();
614 2
        $adminDb = $this->dm->getClient()->selectDatabase('admin');
615
616 2
        return $adminDb->command(
617
            [
618 2
                'shardCollection' => $dbName . '.' . $class->getCollection(),
619 2
                'key'             => $shardKey['keys'],
620
            ]
621 2
        )->toArray()[0];
622
    }
623
624 2
    private function ensureGridFSIndexes(ClassMetadata $class) : void
625
    {
626 2
        $this->ensureChunksIndex($class);
627 2
        $this->ensureFilesIndex($class);
628 2
    }
629
630 2
    private function ensureChunksIndex(ClassMetadata $class) : void
631
    {
632 2
        $chunksCollection = $this->dm->getDocumentBucket($class->getName())->getChunksCollection();
633 2
        foreach ($chunksCollection->listIndexes() as $index) {
634
            if ($index->isUnique() && $index->getKey() === self::GRIDFS_FILE_COLLECTION_INDEX) {
635
                return;
636
            }
637
        }
638
639 2
        $chunksCollection->createIndex(self::GRIDFS_FILE_COLLECTION_INDEX, ['unique' => true]);
640 2
    }
641
642 2
    private function ensureFilesIndex(ClassMetadata $class) : void
643
    {
644 2
        $filesCollection = $this->dm->getDocumentCollection($class->getName());
645 2
        foreach ($filesCollection->listIndexes() as $index) {
646
            if ($index->getKey() === self::GRIDFS_CHUNKS_COLLECTION_INDEX) {
647
                return;
648
            }
649
        }
650
651 2
        $filesCollection->createIndex(self::GRIDFS_CHUNKS_COLLECTION_INDEX);
652 2
    }
653
}
654