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 |
||
19 | class SchemaManager |
||
20 | { |
||
21 | private const GRIDFS_FILE_COLLECTION_INDEX = ['files_id' => 1, 'n' => 1]; |
||
22 | |||
23 | private const GRIDFS_CHUNKS_COLLECTION_INDEX = ['filename' => 1, 'uploadDate' => 1]; |
||
24 | |||
25 | private const CODE_SHARDING_ALREADY_INITIALIZED = 23; |
||
26 | |||
27 | /** @var DocumentManager */ |
||
28 | protected $dm; |
||
29 | |||
30 | /** @var ClassMetadataFactory */ |
||
31 | protected $metadataFactory; |
||
32 | |||
33 | 1668 | public function __construct(DocumentManager $dm, ClassMetadataFactory $cmf) |
|
34 | { |
||
35 | 1668 | $this->dm = $dm; |
|
36 | 1668 | $this->metadataFactory = $cmf; |
|
37 | 1668 | } |
|
38 | |||
39 | /** |
||
40 | * Ensure indexes are created for all documents that can be loaded with the |
||
41 | * metadata factory. |
||
42 | */ |
||
43 | 1 | public function ensureIndexes(?int $timeout = null) : void |
|
44 | { |
||
45 | 1 | foreach ($this->metadataFactory->getAllMetadata() as $class) { |
|
46 | 1 | if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
|
47 | 1 | continue; |
|
48 | } |
||
49 | |||
50 | 1 | $this->ensureDocumentIndexes($class->name, $timeout); |
|
51 | } |
||
52 | 1 | } |
|
53 | |||
54 | /** |
||
55 | * Ensure indexes exist for all mapped document classes. |
||
56 | * |
||
57 | * Indexes that exist in MongoDB but not the document metadata will be |
||
58 | * deleted. |
||
59 | */ |
||
60 | public function updateIndexes(?int $timeout = null) : void |
||
61 | { |
||
62 | foreach ($this->metadataFactory->getAllMetadata() as $class) { |
||
63 | if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
||
64 | continue; |
||
65 | } |
||
66 | |||
67 | $this->updateDocumentIndexes($class->name, $timeout); |
||
68 | } |
||
69 | } |
||
70 | |||
71 | /** |
||
72 | * Ensure indexes exist for the mapped document class. |
||
73 | * |
||
74 | * Indexes that exist in MongoDB but not the document metadata will be |
||
75 | * deleted. |
||
76 | * |
||
77 | * @throws InvalidArgumentException |
||
78 | */ |
||
79 | 3 | public function updateDocumentIndexes(string $documentName, ?int $timeout = null) : void |
|
80 | { |
||
81 | 3 | $class = $this->dm->getClassMetadata($documentName); |
|
82 | |||
83 | 3 | if ($class->isMappedSuperclass || $class->isEmbeddedDocument || $class->isQueryResultDocument) { |
|
84 | throw new InvalidArgumentException('Cannot update document indexes for mapped super classes, embedded documents or aggregation result documents.'); |
||
85 | } |
||
86 | |||
87 | 3 | $documentIndexes = $this->getDocumentIndexes($documentName); |
|
88 | 3 | $collection = $this->dm->getDocumentCollection($documentName); |
|
89 | 3 | $mongoIndexes = iterator_to_array($collection->listIndexes()); |
|
90 | |||
91 | /* Determine which Mongo indexes should be deleted. Exclude the ID index |
||
92 | * and those that are equivalent to any in the class metadata. |
||
93 | */ |
||
94 | 3 | $self = $this; |
|
95 | $mongoIndexes = array_filter($mongoIndexes, static function (IndexInfo $mongoIndex) use ($documentIndexes, $self) { |
||
96 | 1 | if ($mongoIndex['name'] === '_id_') { |
|
97 | return false; |
||
98 | } |
||
99 | |||
100 | 1 | foreach ($documentIndexes as $documentIndex) { |
|
101 | 1 | if ($self->isMongoIndexEquivalentToDocumentIndex($mongoIndex, $documentIndex)) { |
|
102 | 1 | return false; |
|
103 | } |
||
104 | } |
||
105 | |||
106 | 1 | return true; |
|
107 | 3 | }); |
|
108 | |||
109 | // Delete indexes that do not exist in class metadata |
||
110 | 3 | foreach ($mongoIndexes as $mongoIndex) { |
|
111 | 1 | if (! isset($mongoIndex['name'])) { |
|
112 | continue; |
||
113 | } |
||
114 | |||
115 | 1 | $collection->dropIndex($mongoIndex['name']); |
|
116 | } |
||
117 | |||
118 | 3 | $this->ensureDocumentIndexes($documentName, $timeout); |
|
119 | 3 | } |
|
120 | |||
121 | 23 | public function getDocumentIndexes(string $documentName) : array |
|
126 | |||
127 | 23 | private function doGetDocumentIndexes(string $documentName, array &$visited) : array |
|
128 | { |
||
129 | 23 | if (isset($visited[$documentName])) { |
|
130 | 1 | return []; |
|
185 | |||
186 | 23 | private function prepareIndexes(ClassMetadata $class) : array |
|
213 | |||
214 | /** |
||
215 | * Ensure the given document's indexes are created. |
||
216 | * |
||
217 | * @throws InvalidArgumentException |
||
218 | */ |
||
219 | 19 | public function ensureDocumentIndexes(string $documentName, ?int $timeoutMs = null) : void |
|
247 | |||
248 | /** |
||
249 | * Delete indexes for all documents that can be loaded with the |
||
250 | * metadata factory. |
||
251 | */ |
||
252 | 1 | public function deleteIndexes() : void |
|
262 | |||
263 | /** |
||
264 | * Delete the given document's indexes. |
||
265 | * |
||
266 | * @throws InvalidArgumentException |
||
267 | */ |
||
268 | 2 | public function deleteDocumentIndexes(string $documentName) : void |
|
277 | |||
278 | /** |
||
279 | * Create all the mapped document collections in the metadata factory. |
||
280 | */ |
||
281 | 1 | public function createCollections() : void |
|
290 | |||
291 | /** |
||
292 | * Create the document collection for a mapped class. |
||
293 | * |
||
294 | * @throws InvalidArgumentException |
||
295 | */ |
||
296 | 5 | public function createDocumentCollection(string $documentName) : void |
|
320 | |||
321 | /** |
||
322 | * Drop all the mapped document collections in the metadata factory. |
||
323 | */ |
||
324 | 1 | public function dropCollections() : void |
|
334 | |||
335 | /** |
||
336 | * Drop the document collection for a mapped class. |
||
337 | * |
||
338 | * @throws InvalidArgumentException |
||
339 | */ |
||
340 | 5 | public function dropDocumentCollection(string $documentName) : void |
|
355 | |||
356 | /** |
||
357 | * Drop all the mapped document databases in the metadata factory. |
||
358 | */ |
||
359 | 1 | public function dropDatabases() : void |
|
369 | |||
370 | /** |
||
371 | * Drop the document database for a mapped class. |
||
372 | * |
||
373 | * @throws InvalidArgumentException |
||
374 | */ |
||
375 | 2 | public function dropDocumentDatabase(string $documentName) : void |
|
384 | |||
385 | /** |
||
386 | * Determine if an index returned by MongoCollection::getIndexInfo() can be |
||
387 | * considered equivalent to an index in class metadata. |
||
388 | * |
||
389 | * Indexes are considered different if: |
||
390 | * |
||
391 | * (a) Key/direction pairs differ or are not in the same order |
||
392 | * (b) Sparse or unique options differ |
||
393 | * (c) Mongo index is unique without dropDups and mapped index is unique |
||
394 | * with dropDups |
||
395 | * (d) Geospatial options differ (bits, max, min) |
||
396 | * (e) The partialFilterExpression differs |
||
397 | * |
||
398 | * Regarding (c), the inverse case is not a reason to delete and |
||
399 | * recreate the index, since dropDups only affects creation of |
||
400 | * the unique index. Additionally, the background option is only |
||
401 | * relevant to index creation and is not considered. |
||
402 | * |
||
403 | * @param array|IndexInfo $mongoIndex Mongo index data. |
||
404 | */ |
||
405 | 47 | public function isMongoIndexEquivalentToDocumentIndex($mongoIndex, array $documentIndex) : bool |
|
461 | |||
462 | /** |
||
463 | * Determine if the keys for a MongoDB index can be considered equivalent to |
||
464 | * those for an index in class metadata. |
||
465 | * |
||
466 | * @param array|IndexInfo $mongoIndex Mongo index data. |
||
467 | */ |
||
468 | 47 | private function isEquivalentIndexKeys($mongoIndex, array $documentIndex) : bool |
|
492 | |||
493 | /** |
||
494 | * Determine if the text index weights for a MongoDB index can be considered |
||
495 | * equivalent to those for an index in class metadata. |
||
496 | * |
||
497 | * @param array|IndexInfo $mongoIndex Mongo index data. |
||
498 | */ |
||
499 | 14 | private function isEquivalentTextIndexWeights($mongoIndex, array $documentIndex) : bool |
|
524 | |||
525 | /** |
||
526 | * Ensure collections are sharded for all documents that can be loaded with the |
||
527 | * metadata factory. |
||
528 | * |
||
529 | * @throws MongoDBException |
||
530 | */ |
||
531 | public function ensureSharding() : void |
||
541 | |||
542 | /** |
||
543 | * Ensure sharding for collection by document name. |
||
544 | * |
||
545 | * @throws MongoDBException |
||
546 | */ |
||
547 | 5 | public function ensureDocumentSharding(string $documentName) : void |
|
566 | |||
567 | /** |
||
568 | * Enable sharding for database which contains documents with given name. |
||
569 | * |
||
570 | * @throws MongoDBException |
||
571 | */ |
||
572 | 5 | public function enableShardingForDbByDocumentName(string $documentName) : void |
|
588 | |||
589 | 5 | private function runShardCollectionCommand(string $documentName) : array |
|
603 | |||
604 | 2 | private function ensureGridFSIndexes(ClassMetadata $class) : void |
|
609 | |||
610 | 2 | private function ensureChunksIndex(ClassMetadata $class) : void |
|
621 | |||
622 | 2 | private function ensureFilesIndex(ClassMetadata $class) : void |
|
633 | |||
634 | 5 | private function collectionIsSharded(string $documentName) : bool |
|
647 | } |
||
648 |
If you access a property on an interface, you most likely code against a concrete implementation of the interface.
Available Fixes
Adding an additional type check:
Changing the type hint: