Completed
Pull Request — master (#1848)
by Andreas
43:28 queued 18:32
created

SchemaManager::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 2
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 function iterator_count;
10
use MongoDB\Driver\Exception\RuntimeException;
11
use MongoDB\Driver\Exception\ServerException;
12
use MongoDB\Model\IndexInfo;
13
use function array_filter;
14
use function array_unique;
15
use function count;
16
use function iterator_to_array;
17
use function ksort;
18
use function strpos;
19
20
class SchemaManager
21
{
22
    private const GRIDFS_FILE_COLLECTION_INDEX = ['files_id' => 1, 'n' => 1];
23
24
    private const GRIDFS_CHUNKS_COLLECTION_INDEX = ['filename' => 1, 'uploadDate' => 1];
25
26
    private const CODE_SHARDING_ALREADY_INITIALIZED = 23;
27
28
    /** @var DocumentManager */
29 1665
    protected $dm;
30
31 1665
    /** @var ClassMetadataFactory */
32 1665
    protected $metadataFactory;
33 1665
34
    public function __construct(DocumentManager $dm, ClassMetadataFactory $cmf)
35
    {
36
        $this->dm = $dm;
37
        $this->metadataFactory = $cmf;
38
    }
39 1
40
    /**
41 1
     * Ensure indexes are created for all documents that can be loaded with the
42 1
     * metadata factory.
43 1
     */
44
    public function ensureIndexes(?int $timeout = null): void
45
    {
46 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
47
            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...
48 1
                continue;
49
            }
50
51
            $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...
52
        }
53
    }
54
55
    /**
56
     * Ensure indexes exist for all mapped document classes.
57
     *
58
     * Indexes that exist in MongoDB but not the document metadata will be
59
     * deleted.
60
     */
61
    public function updateIndexes(?int $timeout = null): void
62
    {
63
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
64
            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...
65
                continue;
66
            }
67
68
            $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...
69
        }
70
    }
71
72
    /**
73
     * Ensure indexes exist for the mapped document class.
74
     *
75 3
     * Indexes that exist in MongoDB but not the document metadata will be
76
     * deleted.
77 3
     *
78
     * @throws \InvalidArgumentException
79 3
     */
80
    public function updateDocumentIndexes(string $documentName, ?int $timeout = null): void
81
    {
82
        $class = $this->dm->getClassMetadata($documentName);
83 3
84 3
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
85 3
            throw new \InvalidArgumentException('Cannot update document indexes for mapped super classes, embedded documents or aggregation result documents.');
86
        }
87
88
        $documentIndexes = $this->getDocumentIndexes($documentName);
89
        $collection = $this->dm->getDocumentCollection($documentName);
90 3
        $mongoIndexes = iterator_to_array($collection->listIndexes());
91
92 1
        /* Determine which Mongo indexes should be deleted. Exclude the ID index
93
         * and those that are equivalent to any in the class metadata.
94
         */
95
        $self = $this;
96 1
        $mongoIndexes = array_filter($mongoIndexes, function (IndexInfo $mongoIndex) use ($documentIndexes, $self) {
97 1
            if ($mongoIndex['name'] === '_id_') {
98 1
                return false;
99
            }
100
101
            foreach ($documentIndexes as $documentIndex) {
102 1
                if ($self->isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)) {
103 3
                    return false;
104
                }
105
            }
106 3
107 1
            return true;
108
        });
109
110
        // Delete indexes that do not exist in class metadata
111 1
        foreach ($mongoIndexes as $mongoIndex) {
112
            if (! isset($mongoIndex['name'])) {
113
                continue;
114 3
            }
115 3
116
            $collection->dropIndex($mongoIndex['name']);
117 20
        }
118
119 20
        $this->ensureDocumentIndexes($documentName, $timeout);
120 20
    }
121
122
    public function getDocumentIndexes(string $documentName): array
123 20
    {
124
        $visited = [];
125 20
        return $this->doGetDocumentIndexes($documentName, $visited);
126 1
    }
127
128
    private function doGetDocumentIndexes(string $documentName, array &$visited): array
129 20
    {
130
        if (isset($visited[$documentName])) {
131 20
            return [];
132 20
        }
133 20
134
        $visited[$documentName] = true;
135
136 20
        $class = $this->dm->getClassMetadata($documentName);
137 20
        $indexes = $this->prepareIndexes($class);
138 4
        $embeddedDocumentIndexes = [];
139 4
140 2
        // Add indexes from embedded & referenced documents
141 1
        foreach ($class->fieldMappings as $fieldMapping) {
142
            if (isset($fieldMapping['embedded'])) {
143 1
                if (isset($fieldMapping['targetDocument'])) {
144
                    $possibleEmbeds = [$fieldMapping['targetDocument']];
145
                } elseif (isset($fieldMapping['discriminatorMap'])) {
146 4
                    $possibleEmbeds = array_unique($fieldMapping['discriminatorMap']);
147 4
                } else {
148 2
                    continue;
149
                }
150 4
151 4
                foreach ($possibleEmbeds as $embed) {
152
                    if (isset($embeddedDocumentIndexes[$embed])) {
153
                        $embeddedIndexes = $embeddedDocumentIndexes[$embed];
154 4
                    } else {
155 2
                        $embeddedIndexes = $this->doGetDocumentIndexes($embed, $visited);
156 2
                        $embeddedDocumentIndexes[$embed] = $embeddedIndexes;
157 2
                    }
158
159
                    foreach ($embeddedIndexes as $embeddedIndex) {
160 4
                        foreach ($embeddedIndex['keys'] as $key => $value) {
161
                            $embeddedIndex['keys'][$fieldMapping['name'] . '.' . $key] = $value;
162
                            unset($embeddedIndex['keys'][$key]);
163 20
                        }
164 9
165 8
                        $indexes[] = $embeddedIndex;
166 8
                    }
167 8
                }
168 2
            } elseif (isset($fieldMapping['reference']) && isset($fieldMapping['targetDocument'])) {
169
                foreach ($indexes as $idx => $index) {
170
                    $newKeys = [];
171 8
                    foreach ($index['keys'] as $key => $v) {
172
                        if ($key === $fieldMapping['name']) {
173
                            $key = ClassMetadata::getReferenceFieldName($fieldMapping['storeAs'], $key);
174 20
                        }
175
176
                        $newKeys[$key] = $v;
177
                    }
178
179 20
                    $indexes[$idx]['keys'] = $newKeys;
180
                }
181
            }
182 20
        }
183
184 20
        return $indexes;
185 20
    }
186 20
187
    private function prepareIndexes(ClassMetadata $class): array
188 20
    {
189
        $persister = $this->dm->getUnitOfWork()->getDocumentPersister($class->name);
190 19
        $indexes = $class->getIndexes();
191 19
        $newIndexes = [];
192
193
        foreach ($indexes as $index) {
194 19
            $newIndex = [
195 19
                'keys' => [],
196 19
                'options' => $index['options'],
197 17
            ];
198 17
199
            foreach ($index['keys'] as $key => $value) {
200 19
                $key = $persister->prepareFieldName($key);
201
                if ($class->hasField($key)) {
202
                    $mapping = $class->getFieldMapping($key);
203
                    $newIndex['keys'][$mapping['name']] = $value;
204 19
                } else {
205
                    $newIndex['keys'][$key] = $value;
206
                }
207 20
            }
208
209
            $newIndexes[] = $newIndex;
210
        }
211
212
        return $newIndexes;
213
    }
214
215 16
    /**
216
     * Ensure the given document's indexes are created.
217 16
     *
218 16
     * @throws \InvalidArgumentException
219
     */
220
    public function ensureDocumentIndexes(string $documentName, ?int $timeoutMs = null): void
221
    {
222 16
        $class = $this->dm->getClassMetadata($documentName);
223 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
224
            throw new \InvalidArgumentException('Cannot create document indexes for mapped super classes, embedded documents or query result documents.');
225
        }
226 16
227 16
        if ($class->isFile) {
228 4
            $this->ensureGridFSIndexes($class);
229
        }
230
231 15
        $indexes = $this->getDocumentIndexes($documentName);
232 15
        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...
233 15
            return;
234 15
        }
235
236 15
        $collection = $this->dm->getDocumentCollection($class->name);
237 1
        foreach ($indexes as $index) {
238
            $keys = $index['keys'];
239
            $options = $index['options'];
240 15
241
            if (! isset($options['timeout']) && isset($timeoutMs)) {
242 15
                $options['timeout'] = $timeoutMs;
243
            }
244
245
            $collection->createIndex($keys, $options);
246
        }
247
    }
248 1
249
    /**
250 1
     * Delete indexes for all documents that can be loaded with the
251 1
     * metadata factory.
252 1
     */
253
    public function deleteIndexes(): void
254
    {
255 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
256
            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...
257 1
                continue;
258
            }
259
260
            $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...
261
        }
262
    }
263
264 2
    /**
265
     * Delete the given document's indexes.
266 2
     *
267 2
     * @throws \InvalidArgumentException
268
     */
269
    public function deleteDocumentIndexes(string $documentName): void
270
    {
271 2
        $class = $this->dm->getClassMetadata($documentName);
272 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
273
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or query result documents.');
274
        }
275
276
        $this->dm->getDocumentCollection($documentName)->dropIndexes();
277 1
    }
278
279 1
    /**
280 1
     * Create all the mapped document collections in the metadata factory.
281 1
     */
282
    public function createCollections(): void
283 1
    {
284
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
285 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...
286
                continue;
287
            }
288
            $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...
289
        }
290
    }
291
292 5
    /**
293
     * Create the document collection for a mapped class.
294 5
     *
295
     * @throws \InvalidArgumentException
296 5
     */
297
    public function createDocumentCollection(string $documentName): void
298
    {
299
        $class = $this->dm->getClassMetadata($documentName);
300 5
301 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
302 2
            throw new \InvalidArgumentException('Cannot create document collection for mapped super classes, embedded documents or query result documents.');
303
        }
304 2
305
        if ($class->isFile) {
306
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getBucketName() . '.files');
307 4
            $this->dm->getDocumentDatabase($documentName)->createCollection($class->getBucketName() . '.chunks');
308 4
309
            return;
310 4
        }
311 4
312 4
        $this->dm->getDocumentDatabase($documentName)->createCollection(
313
            $class->getCollection(),
314
            [
315 4
                'capped' => $class->getCollectionCapped(),
316
                'size' => $class->getCollectionSize(),
317
                'max' => $class->getCollectionMax(),
318
            ]
319
        );
320 1
    }
321
322 1
    /**
323 1
     * Drop all the mapped document collections in the metadata factory.
324 1
     */
325
    public function dropCollections(): void
326
    {
327 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
328
            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...
329 1
                continue;
330
            }
331
332
            $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...
333
        }
334
    }
335
336 5
    /**
337
     * Drop the document collection for a mapped class.
338 5
     *
339 5
     * @throws \InvalidArgumentException
340
     */
341
    public function dropDocumentCollection(string $documentName): void
342
    {
343 5
        $class = $this->dm->getClassMetadata($documentName);
344
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
345 5
            throw new \InvalidArgumentException('Cannot delete document indexes for mapped super classes, embedded documents or query result documents.');
346 4
        }
347
348
        $this->dm->getDocumentCollection($documentName)->drop();
349 2
350 2
        if (! $class->isFile) {
351
            return;
352
        }
353
354
        $this->dm->getDocumentBucket($documentName)->getChunksCollection()->drop();
355 1
    }
356
357 1
    /**
358 1
     * Drop all the mapped document databases in the metadata factory.
359 1
     */
360
    public function dropDatabases(): void
361
    {
362 1
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
363
            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...
364 1
                continue;
365
            }
366
367
            $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...
368
        }
369
    }
370
371 2
    /**
372
     * Drop the document database for a mapped class.
373 2
     *
374 2
     * @throws \InvalidArgumentException
375
     */
376
    public function dropDocumentDatabase(string $documentName): void
377
    {
378 2
        $class = $this->dm->getClassMetadata($documentName);
379 2
        if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) {
380
            throw new \InvalidArgumentException('Cannot drop document database for mapped super classes, embedded documents or query result documents.');
381
        }
382
383
        $this->dm->getDocumentDatabase($documentName)->drop();
384
    }
385
386
    /**
387
     * Determine if an index returned by MongoCollection::getIndexInfo() can be
388
     * considered equivalent to an index in class metadata.
389
     *
390
     * Indexes are considered different if:
391
     *
392
     *   (a) Key/direction pairs differ or are not in the same order
393
     *   (b) Sparse or unique options differ
394
     *   (c) Mongo index is unique without dropDups and mapped index is unique
395
     *       with dropDups
396
     *   (d) Geospatial options differ (bits, max, min)
397
     *   (e) The partialFilterExpression differs
398
     *
399
     * Regarding (c), the inverse case is not a reason to delete and
400
     * recreate the index, since dropDups only affects creation of
401 47
     * the unique index. Additionally, the background option is only
402
     * relevant to index creation and is not considered.
403 47
     *
404
     * @param array|IndexInfo $mongoIndex Mongo index data.
405 47
     */
406 4
    public function isMongoIndexEquivalentToDocumentIndex($mongoIndex, array $documentIndex): bool
407
    {
408
        $documentIndexOptions = $documentIndex['options'];
409 43
410 2
        if (! $this->isEquivalentIndexKeys($mongoIndex, $documentIndex)) {
411
            return false;
412
        }
413 41
414 2
        if (empty($mongoIndex['sparse']) xor empty($documentIndexOptions['sparse'])) {
415
            return false;
416
        }
417 39
418 39
        if (empty($mongoIndex['unique']) xor empty($documentIndexOptions['unique'])) {
419 1
            return false;
420
        }
421
422 38
        if (! empty($mongoIndex['unique']) && empty($mongoIndex['dropDups']) &&
423 38
            ! empty($documentIndexOptions['unique']) && ! empty($documentIndexOptions['dropDups'])) {
424 6
            return false;
425
        }
426
427 36
        foreach (['bits', 'max', 'min'] as $option) {
428 36
            if (isset($mongoIndex[$option]) xor isset($documentIndexOptions[$option])) {
429 36
                return false;
430
            }
431
432
            if (isset($mongoIndex[$option], $documentIndexOptions[$option]) &&
433 29
                $mongoIndex[$option] !== $documentIndexOptions[$option]) {
434 2
                return false;
435
            }
436
        }
437 27
438 27
        if (empty($mongoIndex['partialFilterExpression']) xor empty($documentIndexOptions['partialFilterExpression'])) {
439 1
            return false;
440
        }
441
442 26
        if (isset($mongoIndex['partialFilterExpression'], $documentIndexOptions['partialFilterExpression']) &&
443 2
            $mongoIndex['partialFilterExpression'] !== $documentIndexOptions['partialFilterExpression']) {
444
            return false;
445
        }
446 24
447
        if (isset($mongoIndex['weights']) && ! $this->isEquivalentTextIndexWeights($mongoIndex, $documentIndex)) {
448
            return false;
449 24
        }
450 24
451 24
        foreach (['default_language', 'language_override', 'textIndexVersion'] as $option) {
452
            /* Text indexes will always report defaults for these options, so
453
             * only compare if we have explicit values in the document index. */
454
            if (isset($mongoIndex[$option], $documentIndexOptions[$option]) &&
455 21
                $mongoIndex[$option] !== $documentIndexOptions[$option]) {
456
                return false;
457
            }
458
        }
459
460
        return true;
461
    }
462
463
    /**
464 47
     * Determine if the keys for a MongoDB index can be considered equivalent to
465
     * those for an index in class metadata.
466 47
     *
467 47
     * @param array|IndexInfo $mongoIndex Mongo index data.
468
     */
469
    private function isEquivalentIndexKeys($mongoIndex, array $documentIndex): bool
470
    {
471
        $mongoIndexKeys    = $mongoIndex['key'];
472
        $documentIndexKeys = $documentIndex['keys'];
473
474 47
        /* If we are dealing with text indexes, we need to unset internal fields
475 15
         * from the MongoDB index and filter out text fields from the document
476
         * index. This will leave only non-text fields, which we can compare as
477
         * normal. Any text fields in the document index will be compared later
478 15
         * with isEquivalentTextIndexWeights(). */
479 15
        if (isset($mongoIndexKeys['_fts']) && $mongoIndexKeys['_fts'] === 'text') {
480
            unset($mongoIndexKeys['_fts'], $mongoIndexKeys['_ftsx']);
481
482
            $documentIndexKeys = array_filter($documentIndexKeys, function ($type) {
483
                return $type !== 'text';
484
            });
485
        }
486 47
487
        /* Avoid a strict equality check here. The numeric type returned by
488
         * MongoDB may differ from the document index without implying that the
489
         * indexes themselves are inequivalent. */
490
        // phpcs:disable SlevomatCodingStandard.ControlStructures.DisallowEqualOperators.DisallowedEqualOperator
491
        return $mongoIndexKeys == $documentIndexKeys;
492
    }
493
494
    /**
495 14
     * Determine if the text index weights for a MongoDB index can be considered
496
     * equivalent to those for an index in class metadata.
497 14
     *
498 14
     * @param array|IndexInfo $mongoIndex Mongo index data.
499
     */
500
    private function isEquivalentTextIndexWeights($mongoIndex, array $documentIndex): bool
501 14
    {
502 14
        $mongoIndexWeights    = $mongoIndex['weights'];
503 5
        $documentIndexWeights = $documentIndex['options']['weights'] ?? [];
504
505
        // If not specified, assign a default weight for text fields
506 9
        foreach ($documentIndex['keys'] as $key => $type) {
507
            if ($type !== 'text' || isset($documentIndexWeights[$key])) {
508
                continue;
509
            }
510
511 14
            $documentIndexWeights[$key] = 1;
512 14
        }
513
514
        /* MongoDB returns the weights sorted by field name, but we'll sort both
515
         * arrays in case that is internal behavior not to be relied upon. */
516
        ksort($mongoIndexWeights);
517
        ksort($documentIndexWeights);
518 14
519
        /* Avoid a strict equality check here. The numeric type returned by
520
         * MongoDB may differ from the document index without implying that the
521
         * indexes themselves are inequivalent. */
522
        // phpcs:disable SlevomatCodingStandard.ControlStructures.DisallowEqualOperators.DisallowedEqualOperator
523
        return $mongoIndexWeights == $documentIndexWeights;
524
    }
525
526
    /**
527
     * Ensure collections are sharded for all documents that can be loaded with the
528
     * metadata factory.
529
     *
530
     * @throws MongoDBException
531
     */
532
    public function ensureSharding(): void
533
    {
534
        foreach ($this->metadataFactory->getAllMetadata() as $class) {
535
            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...
536
                continue;
537
            }
538
539
            $this->ensureDocumentSharding($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...
540
        }
541
    }
542
543
    /**
544
     * Ensure sharding for collection by document name.
545
     *
546
     * @throws MongoDBException
547 2
     */
548
    public function ensureDocumentSharding(string $documentName): void
549 2
    {
550 2
        $class = $this->dm->getClassMetadata($documentName);
551
        if (! $class->isSharded()) {
552
            return;
553
        }
554 2
555
        if ($this->collectionIsSharded($documentName)) {
556 2
            return;
557
        }
558
559 2
        $this->enableShardingForDbByDocumentName($documentName);
560 2
561
        try {
562
            $this->runShardCollectionCommand($documentName);
563 2
        } 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...
564
            throw MongoDBException::failedToEnsureDocumentSharding($documentName, $e->getMessage());
565
        }
566
    }
567
568
    /**
569 2
     * Enable sharding for database which contains documents with given name.
570
     *
571 1
     * @throws MongoDBException
572 1
     */
573 1
    public function enableShardingForDbByDocumentName(string $documentName): void
574
    {
575
        $dbName = $this->dm->getDocumentDatabase($documentName)->getDatabaseName();
576
        $adminDb = $this->dm->getClient()->selectDatabase('admin');
577
578 2
        try {
579
            $adminDb->command(['enableSharding' => $dbName]);
580
        } catch (ServerException $e) {
0 ignored issues
show
Bug introduced by
The class MongoDB\Driver\Exception\ServerException 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...
581
            // Don't throw an exception if sharding is already enabled; there's just no other way to check this
582 2
            if ($e->getCode() !== self::CODE_SHARDING_ALREADY_INITIALIZED) {
583 2
                throw MongoDBException::failedToEnableSharding($dbName, $e->getMessage());
584
            }
585
        } 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...
586
            throw MongoDBException::failedToEnableSharding($dbName, $e->getMessage());
587
        }
588
    }
589
590
    private function runShardCollectionCommand(string $documentName): array
591
    {
592
        $class = $this->dm->getClassMetadata($documentName);
593
        $dbName = $this->dm->getDocumentDatabase($documentName)->getDatabaseName();
594 2
        $shardKey = $class->getShardKey();
595
        $adminDb = $this->dm->getClient()->selectDatabase('admin');
596 2
597 2
        $result = $adminDb->command(
598
            [
599
                'shardCollection' => $dbName . '.' . $class->getCollection(),
600 2
                'key'             => $shardKey['keys'],
601 1
            ]
602 1
        )->toArray()[0];
603
604
        return $result;
605
    }
606 2
607
    private function ensureGridFSIndexes(ClassMetadata $class): void
608 2
    {
609
        $this->ensureChunksIndex($class);
610 2
        $this->ensureFilesIndex($class);
611 2
    }
612 2
613 2
    private function ensureChunksIndex(ClassMetadata $class): void
614
    {
615 2
        $chunksCollection = $this->dm->getDocumentBucket($class->getName())->getChunksCollection();
616
        foreach ($chunksCollection->listIndexes() as $index) {
617 2
            if ($index->isUnique() && $index->getKey() === self::GRIDFS_FILE_COLLECTION_INDEX) {
618 2
                return;
619
            }
620 2
        }
621
622 2
        $chunksCollection->createIndex(self::GRIDFS_FILE_COLLECTION_INDEX, ['unique' => true]);
623
    }
624
625 2
    private function ensureFilesIndex(ClassMetadata $class): void
626
    {
627 2
        $filesCollection = $this->dm->getDocumentCollection($class->getName());
628 2
        foreach ($filesCollection->listIndexes() as $index) {
629 2
            if ($index->getKey() === self::GRIDFS_CHUNKS_COLLECTION_INDEX) {
630
                return;
631 2
            }
632
        }
633 2
634 2
        $filesCollection->createIndex(self::GRIDFS_CHUNKS_COLLECTION_INDEX);
635
    }
636
637
    private function collectionIsSharded(string $documentName): bool
638
    {
639
        $class = $this->dm->getClassMetadata($documentName);
640 2
641 2
        $database = $this->dm->getDocumentDatabase($documentName);
642
        $collections = $database->listCollections(['filter' => ['name' => $class->getCollection()]]);
643 2
        if (! iterator_count($collections)) {
644
            return false;
645 2
        }
646 2
647
        $stats = $database->command(['collstats' => $class->getCollection()])->toArray()[0];
648
        return (bool) ($stats['sharded'] ?? false);
649
    }
650
}
651