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