Completed
Push — master ( a8fe50...bce26f )
by Maciej
13s
created

lib/Doctrine/ODM/MongoDB/SchemaManager.php (26 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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